使用JavaScript切分錄音的blob流并生成5秒的wav文件
在使用react-mic進行錄音時,遇到一個需求:需要將錄音的blob流切分成多個5秒的wav文件。然而,嘗試之后發現只有第一個切分的wav文件能夠正常播放,其余文件均提示文件損壞。
在前端實現這個需求時,主要面臨兩個挑戰:一是如何正確切分blob流,二是如何確保每個切分后的片段能夠正確生成并播放wav文件。以下是代碼示例和解決思路:
import React, { useRef, useState } from 'react' import { ReactMic, ReactMicStopEvent } from 'react-mic' import { Button } from 'antd' const AudioRecorder = () => { const [record, setRecord] = useState(false) const resRef = useRef<Blob[]>([]) const audioChunksRef = useRef<Blob[]>([]) const intervalRef = useRef<nodejs.Timer | null>(null) const firstBlob = useRef<Blob | undefined>(undefined) const createWavHeader = (numChannels, sampleRate, byteLength) => { const header = new ArrayBuffer(44); const view = new DataView(header); view.setUint32(0, 1380533830, false); // "RIFF" view.setUint32(4, byteLength + 36, false); view.setUint32(8, 1718449184, false); // "WAVE" view.setUint32(12, 1684108385, false); // "fmt " view.setUint32(16, 16, true); // 16 for PCM view.setUint16(20, 1, true); // PCM view.setUint16(22, numChannels, true); view.setUint32(24, sampleRate, true); view.setUint32(28, sampleRate * numChannels * 2, true); view.setUint16(32, numChannels * 2, true); view.setUint16(34, 16, true); // 16 bits view.setUint32(36, 1684108385, false); // "data" view.setUint32(40, byteLength, true); return header; }; const saveFile = async () => { const chunksList = resRef.current; for (let i = 0; i < chunksList.length; i++) { const audioBuffer = new Uint8Array(await chunksList[i].arrayBuffer()); const header = createWavHeader(1, 44100, audioBuffer.length); // 假設單聲道和 44100Hz const wavBlob = new Blob([header, audioBuffer], { type: 'audio/wav' }); const url = URL.createObjectURL(wavBlob); const a = document.createElement('a'); a.href = url; a.download = `recording${i}.wav`; a.click(); URL.revokeObjectURL(url); } }; const startRecording = () => { setRecord(true) audioChunksRef.current = [] // 清空之前的錄音數據 // 每5秒分割一次錄音 intervalRef.current = setInterval(() => { const curBlob = new Blob(audioChunksRef.current, { type: 'audio/wav' }) const startIndex = audioChunksRef.current.indexOf(firstBlob.current as Blob) const blob = curBlob.slice(startIndex === -1 ? 0 : startIndex, -1, 'audio/wav') firstBlob.current = audioChunksRef.current.at(-1) // 處理當前錄音數據 console.log('分割當前錄音數據:', blob) resRef.current.push(blob) }, 5000) } const stopRecording = () => { setRecord(false) intervalRef.current && clearInterval(intervalRef.current) // 清除定時器 } const onData = (recordedBlob: Blob) => { audioChunksRef.current.push(recordedBlob) // 保存錄音數據 } const onStop = (recordedBlob: ReactMicStopEvent) => { console.log('錄音完成:', recordedBlob) } const saveFile1 = () => { const chunksList = resRef.current chunksList.map(async (v, i) => { const fileName = 'aaa.wav' const file: File = new File([v], fileName, { type: 'audio/wav' }) const fileSize = file.size console.log('fileSize', fileSize) // 創建下載鏈接 const url = URL.createObjectURL(file) const a = document.createElement('a') a.href = url a.download = `recording${i}.wav` // 設置下載文件的名稱 a.click() // 觸發下載 // 釋放URL資源 URL.revokeObjectURL(url) }) } const saveFinalResult = () => { const fileName = 'aaa.wav' const file: File = new File(audioChunksRef.current, fileName, { type: 'audio/wav' }) const fileSize = file.size console.log('fileSize', fileSize) // 創建下載鏈接 const url = URL.createObjectURL(file) const a = document.createElement('a') a.href = url a.download = `recording${Date.now()}.wav` // 設置下載文件的名稱 a.click() // 觸發下載 // 釋放URL資源 URL.revokeObjectURL(url) } return ( <div> <ReactMic record={record} onStop={onStop} onData={onData} mimeType="audio/wav" /> <Button onClick={startRecording}>開始錄音</Button> <Button onClick={stopRecording}>停止錄音</Button> <Button onClick={saveFile}>下載</Button> <Button onClick={saveFinalResult}>下載Final</Button> </div> ) } export default AudioRecorder
在嘗試切分blob流和生成wav文件的過程中,發現手動添加wav頭信息并不能解決問題。其原因在于wav文件的結構比較嚴格,切分后如果不正確地添加頭部信息,文件可能會損壞。
解決這個問題的一個建議是使用ffmpeg的wasm版本,這是一個可以在瀏覽器中運行的音視頻處理庫。通過它,你可以輕松地對音頻進行切分并生成正確的wav文件格式。可以考慮使用ffmpeg.wasm項目來實現這個功能。
? 版權聲明
文章版權歸作者所有,未經允許請勿轉載。
THE END