使用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