JS實(shí)現(xiàn)audio音頻剪裁剪切復(fù)制播放與上傳(步驟詳解)
背景是這樣的,用戶(hù)上傳音頻文件,可能只需要幾十秒就夠了,但是常規(guī)的音樂(lè)都要3~5分鐘,80%的流量都是不需要的,要是就這么傳上去,其實(shí)是流量的浪費(fèi),如果可以在前端就進(jìn)行剪裁,也就是只取前面一段時(shí)間的音頻,豈不是可以給公司省很多流量費(fèi)用,前端的業(yè)務(wù)價(jià)值就體現(xiàn)了。
關(guān)鍵如何實(shí)現(xiàn)呢?
下面,就以“截取用戶(hù)上傳音頻前3秒內(nèi)容”的需求示意下如何借助Web Audio API實(shí)現(xiàn)音頻的部分復(fù)制與播放功能。
一、不嗶嗶,直接正題
實(shí)現(xiàn)步驟如下。
1. File對(duì)象轉(zhuǎn)ArrayBuffer
在Web網(wǎng)頁(yè)中,用戶(hù)選擇的文件是個(gè)file對(duì)象,我們可以將這個(gè)文件對(duì)象轉(zhuǎn)換成Blob、ArrayBuffer或者Base64。
在音頻處理這里,都是使用ArrayBuffer這個(gè)數(shù)據(jù)類(lèi)型。
代碼如下所示,假設(shè)file類(lèi)型的文件選擇框的id是 'file' 。
file.onchange = function (event) {
var file = event.target.files[0];
// 開(kāi)始識(shí)別
var reader = new FileReader();
reader.onload = function (event) {
var arrBuffer = event.target.result;
// arrBuffer就是包含音頻數(shù)據(jù)的ArrayBuffer對(duì)象
});
reader.readAsArrayBuffer(file);
};
使用的是 readAsArrayBuffer() 方法,無(wú)論是MP3格式、OGG格式還是WAV格式,都可以轉(zhuǎn)換成ArrayBuffer類(lèi)型。
2. ArrayBuffer轉(zhuǎn)AudioBuffer
這里的ArrayBuffer相對(duì)于把音頻文件數(shù)組化了,大家可以理解為把音頻文件分解成一段一段的,塞進(jìn)了一個(gè)一個(gè)有地址的小屋子里,在計(jì)算機(jī)領(lǐng)域稱(chēng)為“緩沖區(qū)”,就是單詞Buffer的意思。
所謂音頻的剪裁,其實(shí)就是希望可以復(fù)制音頻前面一段時(shí)間的內(nèi)容。
但是問(wèn)題來(lái)了,ArrayBuffer里面的數(shù)據(jù)并沒(méi)有分類(lèi),統(tǒng)一分解了,想要準(zhǔn)確提取某一截音頻數(shù)據(jù),提取不出來(lái)。
所以,才需要轉(zhuǎn)換成AudioBuffer,純粹的音頻數(shù)據(jù),方便提取。
AudioBuffer是一個(gè)僅僅包含音頻數(shù)據(jù)的數(shù)據(jù)對(duì)象,是Web Audio API中的一個(gè)概念。
既然說(shuō)到了Web Audio API,那我們就順便……順便……,想了想,還是不展開(kāi),因?yàn)樘嬰s了,這Web Audio API至少比Web Animation API復(fù)雜了10倍,API之多,體量之大,世間罕見(jiàn),想要完全吃透了,沒(méi)有三年五載,啃不下來(lái)。
如果大家不是想要立志成為音視頻處理專(zhuān)家,僅僅是臨時(shí)解決一點(diǎn)小毛小病的問(wèn)題,則不必深入,否則腦坑疼,使用MDN文檔中的一些案例東拼西湊,基本的效果也能弄出來(lái)。
扯遠(yuǎn)了,回到這里。
AudioBuffer大家可以理解為音樂(lè)數(shù)據(jù),那為什么叫AudioBuffer,不叫AudioData呢?
因?yàn)锽uffer是個(gè)專(zhuān)有名詞,直譯為緩沖區(qū),大家可以理解為高速公路,AudioBuffer處理數(shù)據(jù)更快,而且還有很多延伸的API,就像是高速公路上的服務(wù)區(qū),有吃有喝還有加油的地方。
AudioData一看名字就是鄉(xiāng)下土鱉,雖然接地氣,但是,處理好幾兆的數(shù)據(jù)的時(shí)候,就有些帶不動(dòng)了,就好像騎小電驢,在公速公路和鄉(xiāng)道縣道沒(méi)多大區(qū)別,但是如果是開(kāi)跑車(chē),嘖嘖,鄉(xiāng)下路就帶不動(dòng)了。
如何才能轉(zhuǎn)換成AudioBuffer呢?
使用AudioContext對(duì)象的 decodeAudioData() 方法,代碼如下:
var audioCtx = new AudioContext();
audioCtx.decodeAudioData(arrBuffer, function(audioBuffer) {
// audioBuffer就是AudioBuffer
});
3. 復(fù)制AudioBuffer前3秒數(shù)據(jù)
AudioBuffer對(duì)象是一個(gè)音頻專(zhuān)用Buffer對(duì)象,包含很多音頻信息,包括:
duration numberOfChannels sampleRate
等。
包括一些音頻聲道數(shù)據(jù)處理方法,例如:
getChannelData() copyFromChannel() copyToChannel()
文檔見(jiàn)這里: https://developer.mozilla.org/en-US/docs/Web/API/AudioBuffer
所以,實(shí)現(xiàn)的原理很簡(jiǎn)單,創(chuàng)建一個(gè)空的AudioBuffer,復(fù)制現(xiàn)有的通道數(shù)據(jù)前3秒的數(shù)據(jù),然后復(fù)制的內(nèi)容寫(xiě)入到這個(gè)空的AudioBuffer,于是我們就得到了一個(gè)剪裁后的音頻Buffer數(shù)據(jù)了。
代碼如下:
// 聲道數(shù)量和采樣率
var channels = audioBuffer.numberOfChannels;
var rate = audioBuffer.sampleRate;
// 截取前3秒
var startOffset = 0;
var endOffset = rate * 3;
// 3秒對(duì)應(yīng)的幀數(shù)
var frameCount = endOffset - startOffset;
// 創(chuàng)建同樣采用率、同樣聲道數(shù)量,長(zhǎng)度是前3秒的空的AudioBuffer
var newAudioBuffer = new AudioContext().createBuffer(channels, endOffset - startOffset, rate);
// 創(chuàng)建臨時(shí)的Array存放復(fù)制的buffer數(shù)據(jù)
var anotherArray = new Float32Array(frameCount);
// 聲道的數(shù)據(jù)的復(fù)制和寫(xiě)入
var offset = 0;
for (var channel = 0; channel < channels; channel++) {
audioBuffer.copyFromChannel(anotherArray, channel, startOffset);
newAudioBuffer.copyToChannel(anotherArray, channel, offset);
}
// newAudioBuffer就是全新的復(fù)制的3秒長(zhǎng)度的AudioBuffer對(duì)象
上面JavaScript代碼中的變量 newAudioBuffer 就是全新的復(fù)制的3秒長(zhǎng)度的AudioBuffer對(duì)象。
4. 使用newAudioBuffer做點(diǎn)什么?
其實(shí)應(yīng)該是有了AudioBuffer對(duì)象后我們可以做點(diǎn)什么。
能做很多事情。
1) 如果希望直接播放
我們可以直接把AudioBuffer的數(shù)據(jù)作為音頻數(shù)據(jù)進(jìn)行播放
// 創(chuàng)建AudioBufferSourceNode對(duì)象 var source = audioCtx.createBufferSource(); // 設(shè)置AudioBufferSourceNode對(duì)象的buffer為復(fù)制的3秒AudioBuffer對(duì)象 source.buffer = newAudioBuffer; // 這一句是必須的,表示結(jié)束,沒(méi)有這一句沒(méi)法播放,沒(méi)有聲音 // 這里直接結(jié)束,實(shí)際上可以對(duì)結(jié)束做一些特效處理 source.connect(audioCtx.destination); // 資源開(kāi)始播放 source.start();
2) 如果希望在<audio>元素中播放
這個(gè)還挺麻煩的。
從 <audio> 的src屬性獲取音頻資源,再進(jìn)行處理是簡(jiǎn)單的,網(wǎng)上的案例也很多。
但是,想要處理后的AudioBuffer再變成src讓 <audio> 元素播放,嘿嘿,就沒(méi)那么容易了。
我 (張?chǎng)涡瘢?找了一圈,沒(méi)有看到Web Audio API中有專(zhuān)門(mén)的“逆轉(zhuǎn)錄”方法。
唯一可行的路數(shù)就是根據(jù)AudioBuffer數(shù)據(jù),重新構(gòu)建原始的音頻數(shù)據(jù)。研究了一番,轉(zhuǎn)成WAV格式相對(duì)容易,想要轉(zhuǎn)換成MP3格式比較麻煩,這里有個(gè)項(xiàng)目: https://github.com/higuma/mp3-lame-encoder-js 不過(guò)自己沒(méi)驗(yàn)證過(guò),不過(guò)看代碼量,還挺驚人的。
因此,我們的目標(biāo)還是轉(zhuǎn)到WAV音頻文件生成上吧,下面這段方法是從網(wǎng)上找的AudioBuffer轉(zhuǎn)WAV文件的方法,以Blob數(shù)據(jù)格式返回。
// 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, sample, 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, data, true);
pos += 4;
}
}
WAV格式的兼容性還是很6的,如下圖所示:

凡事支持Web Audio API的瀏覽器都支持WAV格式,所以,技術(shù)上完全可行。
下面這段JS可以得到剪裁后的WAV音頻的Blob數(shù)據(jù)格式:
var blob = bufferToWave(newAudioBuffer, frameCount);
有了Blob數(shù)據(jù),接下來(lái)事情就簡(jiǎn)單了。
我們可以直接把Blob數(shù)據(jù)轉(zhuǎn)換成URL,可以使用 URL.createObjectURL() 生成一個(gè)Blob鏈接。
假設(shè)頁(yè)面上有如下HTML代碼:
<audio id="audio" controls=""></audio>
則如下設(shè)置,就可以點(diǎn)擊上面的 <audio> 元素進(jìn)行播放了。
audio.src = URL.createObjectURL(blob);
如果要轉(zhuǎn)換成Base64地址,可以這么處理:
var reader2 = new FileReader();
reader2.onload = function(event){
audio.src = event.target.result;
};
reader2.readAsDataURL(blob);
3) 如果希望上傳剪裁的音頻
有了Blob數(shù)據(jù),上傳還不是灑灑水的事情。
可以使用FormData進(jìn)行傳輸,例如:
var formData = new FormData();
formData.append('audio', blob);
// 請(qǐng)求走起
var xhr = new XMLHttpRequest();
xhr.open('POST', this.cgiGetImg, true);
// 請(qǐng)求成功
xhr.onload = function () {
};
// 發(fā)送數(shù)據(jù)
xhr.send(formData);
有demo可以進(jìn)行效果體驗(yàn)的,您可以狠狠地點(diǎn)擊這里: 用戶(hù)上傳的MP3音頻剪裁并播放demo
使用截圖示意如下:

本文地址: https://www.zhangxinxu.com/wordpress/?p=9505
到此這篇關(guān)于JS實(shí)現(xiàn)audio音頻剪裁剪切復(fù)制播放與上傳的文章就介紹到這了,更多相關(guān)js audio音頻剪裁內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
php 解壓zip壓縮包內(nèi)容到指定目錄的實(shí)例
下面小編就為大家分享一篇php 解壓zip壓縮包內(nèi)容到指定目錄的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-01-01
BootStrap自定義popover,點(diǎn)擊區(qū)域隱藏功能的實(shí)現(xiàn)
下面小編就為大家分享一篇BootStrap自定義popover,點(diǎn)擊區(qū)域隱藏功能的實(shí)現(xiàn)方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-01-01
uniapp實(shí)現(xiàn)下拉刷新與上拉觸底加載功能的示例代碼
這篇文章主要記錄一下uniapp實(shí)現(xiàn)下拉刷新與上拉觸底加載功能的示例代碼,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-04-04
JavaScript事件監(jiān)聽(tīng)器詳細(xì)介紹
這篇文章主要介紹了JavaScript事件監(jiān)聽(tīng)器詳細(xì)介紹,文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,感興趣的小伙伴可以參考一下2022-09-09
微信小程序?qū)崿F(xiàn)組件頂端固定或底端固定效果(不隨滾動(dòng)而滾動(dòng))
這篇文章主要介紹了微信小程序?qū)崿F(xiàn)組件頂端固定或底端固定效果(不隨滾動(dòng)而滾動(dòng)),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-04-04
解決layui下拉框監(jiān)聽(tīng)問(wèn)題(監(jiān)聽(tīng)不到值的變化)
今天小編就為大家分享一篇解決layui下拉框監(jiān)聽(tīng)問(wèn)題(監(jiān)聽(tīng)不到值的變化),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-09-09
echarts圖表設(shè)置寬度100%結(jié)果為100px的解決辦法
在開(kāi)發(fā)一個(gè)前端項(xiàng)目時(shí)需要用到Element-ui的el-tabs組件和Echart開(kāi)源庫(kù),當(dāng)兩者嵌套使用時(shí),我給Echart中的圖表寬度設(shè)置為了100%,但是實(shí)際的寬度卻只有100px,這篇文章主要給大家介紹了關(guān)于echarts圖表設(shè)置寬度100%結(jié)果為100px的解決辦法,需要的朋友可以參考下2022-12-12
javascript實(shí)現(xiàn)時(shí)間格式輸出FormatDate函數(shù)
這篇文章主要介紹了javascript實(shí)現(xiàn)時(shí)間格式輸出FormatDate函數(shù),可實(shí)現(xiàn)fmt標(biāo)簽一樣對(duì)日期時(shí)間型內(nèi)容格式輸入的功能,是非常實(shí)用的技巧,需要的朋友可以參考下2015-01-01
uniapp頁(yè)面回到頂部?jī)煞N實(shí)現(xiàn)方法
這篇文章主要給大家介紹了關(guān)于uniapp頁(yè)面回到頂部?jī)煞N實(shí)現(xiàn)方法的相關(guān)資料,在uniapp中要實(shí)現(xiàn)回到頂部的效果有兩種方法實(shí)現(xiàn),文中給出了詳細(xì)的代碼示例,需要的朋友可以參考下2023-08-08

