如何将录音生成的Blob转为MP3并通过POST请求发送?
解决WebM/Opus Blob转MP3并上传的问题
问题本质
你录制生成的是audio/webm;codecs=opus格式的Blob,浏览器没有原生API直接将其转为MP3,必须借助第三方音频编码库实现格式转换,再通过FormData上传。
方案一:使用lamejs(轻量MP3编码库)
lamejs是专门针对MP3编码的轻量级库,适合简单的音频转码场景。
步骤1:解码WebM为PCM数据
首先需要将WebM Blob解码成PCM(脉冲编码调制)原始音频数据,这是编码MP3的前提:
async function webmToPcm(blob) { const audioContext = new (window.AudioContext || window.webkitAudioContext)(); const arrayBuffer = await blob.arrayBuffer(); const audioBuffer = await audioContext.decodeAudioData(arrayBuffer); // 转换为单声道数据(lamejs默认支持单声道,也可扩展为立体声) const channelData = audioBuffer.getChannelData(0); // 将32位浮点PCM转为16位整数PCM(lamejs要求的输入格式) const pcmData = new Int16Array(channelData.length); for (let i = 0; i < channelData.length; i++) { const sample = Math.max(-1, Math.min(1, channelData[i])); pcmData[i] = sample < 0 ? sample * 0x8000 : sample * 0x7FFF; } return { pcm: pcmData, sampleRate: audioBuffer.sampleRate }; }
步骤2:将PCM编码为MP3 Blob
使用lamejs将PCM数据编码为MP3格式:
function pcmToMp3(pcmData, sampleRate) { // 初始化编码器:单声道、采样率、比特率(128kbps) const mp3Encoder = new lamejs.Mp3Encoder(1, sampleRate, 128); const mp3Chunks = []; // 按lamejs推荐的chunk大小分批编码 const chunkSize = 1152; for (let i = 0; i < pcmData.length; i += chunkSize) { const chunk = pcmData.subarray(i, i + chunkSize); const mp3Buf = mp3Encoder.encodeBuffer(chunk); if (mp3Buf.length > 0) { mp3Chunks.push(new Uint8Array(mp3Buf)); } } // 完成编码,获取剩余数据 const endBuf = mp3Encoder.flush(); if (endBuf.length > 0) { mp3Chunks.push(new Uint8Array(endBuf)); } // 合并chunk为MP3 Blob return new Blob(mp3Chunks, { type: 'audio/mpeg' }); }
步骤3:整合上传逻辑
将转换后的MP3 Blob通过FormData上传:
async function sendBlob() { const webmBlob = getBlob; // 替换为你录制得到的Blob对象 try { // 转码为MP3 const pcmInfo = await webmToPcm(webmBlob); const mp3Blob = pcmToMp3(pcmInfo.pcm, pcmInfo.sampleRate); // 构造FormData const formData = new FormData(); // 第三个参数为文件名,可选但建议添加,方便后端识别 formData.append("audio-file", mp3Blob, "recording.mp3"); // 发送请求 const res = await fetch("/api/whisper", { method: "POST", body: formData, }); const { result } = await res.json(); console.log('上传成功:', result); } catch (error) { console.error('转码或上传失败:', error); } }
方案二:使用ffmpeg.wasm(全功能多媒体转码)
如果需要支持更多格式或复杂转码需求,可以使用ffmpeg.wasm,它是FFmpeg的WebAssembly版本,功能更全面但体积较大。
代码示例
// 先通过npm安装:npm install @ffmpeg/ffmpeg @ffmpeg/core import { createFFmpeg, fetchFile } from '@ffmpeg/ffmpeg'; // 初始化FFmpeg实例 const ffmpeg = createFFmpeg({ log: false }); async function webmToMp3(blob) { // 加载FFmpeg核心(首次加载会下载核心文件,建议提前加载) if (!ffmpeg.isLoaded()) { await ffmpeg.load(); } // 将WebM Blob写入FFmpeg内存文件系统 ffmpeg.FS('writeFile', 'input.webm', await fetchFile(blob)); // 执行转码命令:将WebM转为128kbps的MP3 await ffmpeg.run('-i', 'input.webm', '-b:a', '128k', 'output.mp3'); // 读取转码后的MP3文件 const mp3Data = ffmpeg.FS('readFile', 'output.mp3'); // 转为MP3 Blob return new Blob([mp3Data.buffer], { type: 'audio/mpeg' }); } // 上传函数 async function sendBlob() { const webmBlob = getBlob; try { const mp3Blob = await webmToMp3(webmBlob); const formData = new FormData(); formData.append("audio-file", mp3Blob, "recording.mp3"); const res = await fetch("/api/whisper", { method: "POST", body: formData, }); const { result } = await res.json(); console.log('上传成功:', result); } catch (error) { console.error('转码或上传失败:', error); } }
注意事项
- 浏览器限制:
AudioContext需要在用户交互(如点击、触摸)后初始化,确保你的录制功能是在用户触发的事件中启动的,否则会报错。 - 性能考量:前端转码会消耗CPU资源,短音频(如语音消息)没问题,长音频建议考虑后端转码(如果允许将WebM上传到后端再转MP3)。
- 库的引入:lamejs可以通过下载本地脚本引入,ffmpeg.wasm建议通过npm安装或使用本地资源,避免外链依赖。
内容的提问来源于stack exchange,提问作者karim arbia
相关产品推荐
相关产品推荐

