實現純前端下的音訊剪輯處理

2022-07-11 01:54:11 字數 4192 閱讀 9477

最近在做乙個專案,需要對webrtc錄製的音訊進行處理,包括音訊的裁剪、多音訊合併,甚至要將某個音訊的某一部分替換成另乙個音訊。

由於ffmpeg在處理過程中需要大量的計算,直接放在前端頁面上去執行是不可能的,因為我們需要單獨開個web worker,讓它自己在worker裡面執行,而不至於阻塞頁面互動。

可喜的是,萬能的github上已經有開發者提供了ffmpge.js,並且提供worker版本,可以拿來直接使用。

於是我們便有了大體的思路:當獲取到音訊檔案後,將其解碼後傳送給worker,讓其進行計算處理,並將處理結果以事件的方式返回,這樣我們就可以對音訊為所欲為了:)

需要提前宣告的是,由於筆者的專案需求,是僅需對.***格式進行處理的,因此下面的**示例以及倉庫位址裡面所涉及的**,也主要是針對***,當然,其實不管是哪種格式,思路是類似的。

建立worker的方式非常簡單,直接new之,注意的是,由於同源策略的限制,要使worker正常工作,則要與父頁面同源,由於這不是重點,所以略過

function createworker(workerpath: string) 

仔細看ffmpeg.js文件的童鞋都會發現,它在處理音訊的不同階段都會發射事件給父頁面,比如stdout,start和done等等,如果直接為這些事件新增**函式,在**函式裡去區分、處理乙個又乙個音訊的結果,是不大好維護的。個人更傾向於將其轉成promise:

function pmtopromise(worker, postinfo) 

};// 異常捕獲

const failhandler = function(error) ;

worker.addeventlistener("message", successhandler);

worker.addeventlistener("error", failhandler);

postinfo && worker.postmessage(postinfo);

});}

通過這層轉換,我們就可以將一次postmessage請求,轉換成了promise的方式來處理,更易於空間上的拓展

ffmpeg-worker所需要的資料格式是arraybuffer,而一般我們能直接使用的,要麼是音訊檔案物件blob,或者音訊元素物件audio,甚至有可能僅是一條鏈結url,因此這幾種格式的轉換是非常有必要的:

audio轉arraybuffer

function audiotoblob(audio) ).then(res => res.data);

} else

}

筆者暫時想到的audio轉blob的方式,就是發起一段ajax請求,將請求型別設定為arraybuffer,即可拿到arraybuffer.

blob轉arraybuffer

這個也很簡單,只需要借助filereader將blob內容提取出來即可

function blobtoarraybuffer(blob) ;

filereader.readasarraybuffer(blob);

});}

arraybuffer轉blob

利用file建立出乙個blob

function audiobuffertoblob(arraybuffer) );

return file;

}

blob轉audio
function blobtoaudio(blob) 

接下來我們進入正題。

所謂裁剪,即是指將給定的音訊,按給定的起始、結束時間點,提取這部分的內容,形成新的音訊,先上**:

class sdk  else 

// 將worker處理過後的arraybuffer包裝成blob,並返回

return audiobuffertoblob(resultarrbuf);};}

我們定義了該介面的三個引數:需要被剪裁的音訊blob,以及裁剪的開始、結束時間點,值得注意的是這裡的getclipcommand函式,它負責將傳入的arraybuffer、時間包裝成ffmpeg-worker約定的資料格式

/**

* 按ffmpeg文件要求,將帶裁剪資料轉換成指定格式

* @param arraybuffer 待處理的音訊buffer

* @param st 開始裁剪時間點(秒)

* @param duration 裁剪時長

*/function getclipcommand(arraybuffer, st, duration) -i input.*** $ ` : ""

}-acodec copy output.***`.split(" "),

memfs: };

}

多音訊合成很好理解,即將多個音訊按陣列先後順序合併成乙個音訊

class sdk 

const result = await pmtopromise(

this.worker,

await getcombinecommand(arrbufs),

);return audiobuffertoblob(result.data.data.memfs[0].data);};}

上述**中,我們是通過for迴圈來將陣列裡的blob乙個個解碼成arraybuffer,可能有童鞋會好奇:為什麼不直接使用陣列自帶的foreach方法去遍歷呢?寫for迴圈未免麻煩了點。其實是有原因的:我們在迴圈體裡使用了await,是期望這些blob乙個個解碼完成後,才執行後面的**,for迴圈是同步執行的,但foreach的每個迴圈體是分別非同步執行的,我們無法通過await的方式等待它們全部執行完成,因此使用foreach並不符合我們的預期。

同樣,getcombinecommand函式的職責與上述getclipcommand類似:

async function getcombinecommand(arraybuffers) .***`,

}));

// 建立乙個txt文字,用於告訴ffmpeg我們所需進行合併的音訊檔案有哪些(類似這些檔案的乙個對映表)

const txtcontent = [files.map(f => `file '$'`).join('\n')];

const txtblob = new blob(txtcontent, );

const filearraybuffer = await blobtoarraybuffer(txtblob);

// 將txt檔案也一併推入到即將傳送給ffmpeg-worker的檔案列表中

files.push();

return ;

}

在上面**中,與裁剪操作不同的是,被操作的音訊物件不止乙個,而是多個,因此需要建立乙個「對映表」去告訴ffmpeg-worker一共需要合併哪些音訊以及它們的合併順序。

廣州品牌設計公司

它有點類似clip的公升級版,我們從指定的位置刪除音訊a,並在此處插入音訊b:

class sdk  else if (ss === 0)  else if (ss !== 0 && es === this.end)  else 

// 將多個音訊重新合併

const arrbufs = ;

leftsidearrbuf && arrbufs.push(leftsidearrbuf);

insertblob && arrbufs.push(await blobtoarraybuffer(insertblob));

rightsidearrbuf && arrbufs.push(rightsidearrbuf);

const combindresult = await pmtopromise(

this.worker,

await getcombinecommand(arrbufs)

);return audiobuffertoblob(combindresult.data.data.memfs[0].data);};}

上述**有點類似clip和concat的復合使用。

彈幕的實現(純前端)

一 前言 今天瀏覽某 看到乙個活動頁有內嵌的彈幕模組 圖一 但是看到移動的彈幕重疊很多,不忍直視啊。突然想起很久之前自己寫寫過類似的彈幕,就翻出來看了一下,呵,也是不忍直視的,最後再附上當年的效果以及 二 大話幾點 5 彈幕的後台實現可以通過websocket實現,當然也可以借助node實現。當使用...

Vue Element UI實現純前端分頁

思路 我們在請求後端拿到的資料往往有很多條,這時候我們就希望通過分頁讓頁面上只展示某部分資料 如何展示某部分資料呢?既然是分頁,那麼必定會涉及到兩個東西 一 每一頁的資料條數,設它為pagenum 二 當前頁,就是當前頁是第幾頁,設它為currentpage 那麼我們就可以根據這兩個變數進行計算,計...

純前端vue實現登入的驗證碼

以前是把驗證碼位址和id存在後端redis中,每次請求失敗或成功都作廢一組鍵值對 對於我的小伺服器為了節省資源,我覺得把驗證碼放在前端生成,進行生成驗證 前端客戶端呼叫js,大部分還是前端來提供效能支撐 驗證碼元件 components validcode.vue views login.vue中呼...