如何通过Web Audio API获取Wav格式而非Ogg格式音频?
为什么MediaRecorder无法生成WAV格式,以及解决方案
这是正常现象,原因如下:
MediaRecorder的输出格式由浏览器支持的音频编码/容器组合决定,不是单纯修改Blob的type参数就能改变的。目前主流浏览器(Chrome、Firefox等)的MediaRecorder默认只支持audio/ogg; codecs=opus这种压缩格式,对无压缩的WAV格式支持非常有限(几乎没有浏览器原生支持用MediaRecorder录制WAV)。你修改Blob的type只是给文件打了个格式标签,但实际内容还是Ogg编码的,自然无法被识别为WAV。
解决方案
方案1:前端直接生成WAV文件
绕过MediaRecorder,从Web Audio API获取原始PCM音频数据,手动构建WAV文件的头信息和数据。这种方式不需要依赖后端,生成的WAV可以直接在所有播放器中播放。
示例代码(基于OfflineAudioContext渲染处理后的音频,再转WAV):
// 将AudioBuffer转换为WAV格式的Blob function audioBufferToWav(audioBuffer) { const channelCount = audioBuffer.numberOfChannels; const sampleRate = audioBuffer.sampleRate; const length = audioBuffer.length * channelCount * 2; // 16位深度,每个样本2字节 const buffer = new ArrayBuffer(44 + length); // WAV头占44字节 const view = new DataView(buffer); // 写入WAV文件头 let offset = 0; // RIFF chunk view.setUint32(offset, 0x46464952, true); offset += 4; // "RIFF" view.setUint32(offset, 36 + length, true); offset += 4; // 文件长度 view.setUint32(offset, 0x45564157, true); offset += 4; // "WAVE" // fmt subchunk view.setUint32(offset, 0x20746d66, true); offset += 4; // "fmt " view.setUint32(offset, 16, true); offset += 4; // 子块长度(PCM格式固定16) view.setUint16(offset, 1, true); offset += 2; // 音频格式(PCM=1) view.setUint16(offset, channelCount, true); offset += 2; // 声道数 view.setUint32(offset, sampleRate, true); offset += 4; // 采样率 view.setUint32(offset, sampleRate * channelCount * 2, true); offset += 4; // 字节率 view.setUint16(offset, channelCount * 2, true); offset += 2; // 块对齐 view.setUint16(offset, 16, true); offset += 2; // 位深度 // data subchunk view.setUint32(offset, 0x61746164, true); offset += 4; // "data" view.setUint32(offset, length, true); offset += 4; // 写入PCM采样数据 const channels = []; for (let i = 0; i < channelCount; i++) { channels.push(audioBuffer.getChannelData(i)); } let sampleIndex = 0; while (offset < buffer.byteLength) { for (let channel = 0; channel < channelCount; channel++) { // 将[-1, 1]范围的浮点样本转换为16位整数 const sample = Math.max(-1, Math.min(1, channels[channel][sampleIndex])); const intSample = sample < 0 ? sample * 0x8000 : sample * 0x7FFF; view.setInt16(offset, intSample, true); offset += 2; } sampleIndex++; } return new Blob([buffer], { type: 'audio/wav' }); } // 渲染处理后的音频到AudioBuffer async function renderProcessedAudio(context, processedNodes, duration) { // 使用OfflineAudioContext离线渲染,避免实时播放 const offlineCtx = new OfflineAudioContext( context.destination.channelCount, context.sampleRate * duration, context.sampleRate ); // 复制处理后的节点到离线上下文(这里假设你已经有处理好的AudioBufferSourceNode) processedNodes.forEach(node => { const offlineNode = offlineCtx.createBufferSource(); offlineNode.buffer = node.buffer; offlineNode.connect(offlineCtx.destination); offlineNode.start(); }); return offlineCtx.startRendering(); } // 调用示例:合并1-4个处理后的样本并生成WAV async function mergeAndSaveWav(context, processedSamples, duration) { const audioBuffer = await renderProcessedAudio(context, processedSamples, duration); const wavBlob = audioBufferToWav(audioBuffer); // 触发下载 const url = URL.createObjectURL(wavBlob); const a = document.createElement('a'); a.href = url; a.download = 'merged-samples.wav'; a.click(); URL.revokeObjectURL(url); }
方案2:后端用FFmpeg转换Ogg到WAV
如果前端只能生成Ogg,可以在PHP后端调用FFmpeg进行格式转换。你之前遇到的FFmpeg问题,通常是以下原因:
- 服务器未安装FFmpeg,或PHP无法找到FFmpeg的执行路径(需要指定绝对路径,比如
/usr/bin/ffmpeg) - 命令参数错误,比如未指定正确的WAV编码(必须用无压缩的PCM编码,比如
pcm_s16le) - 文件权限问题,PHP没有读写文件的权限
示例PHP代码:
<?php // 假设前端上传的Ogg文件通过表单提交,name为"audio" if (!isset($_FILES['audio']) || $_FILES['audio']['error'] !== UPLOAD_ERR_OK) { die('文件上传失败'); } $tempOggPath = $_FILES['audio']['tmp_name']; $outputWavPath = __DIR__ . '/merged-sample.wav'; // FFmpeg命令:指定输入文件,输出PCM 16位、44.1kHz、立体声的WAV $ffmpegPath = '/usr/bin/ffmpeg'; // 根据服务器实际路径调整 $command = sprintf( '%s -i "%s" -acodec pcm_s16le -ar 44100 -ac 2 "%s"', $ffmpegPath, escapeshellarg($tempOggPath), escapeshellarg($outputWavPath) ); exec($command, $output, $returnCode); if ($returnCode !== 0) { // 输出错误信息排查问题 die('FFmpeg转换失败:' . implode("\n", $output)); } // 返回WAV文件给用户下载 header('Content-Type: audio/wav'); header('Content-Disposition: attachment; filename="merged-sample.wav"'); header('Content-Length: ' . filesize($outputWavPath)); readfile($outputWavPath); // 清理临时文件 unlink($outputWavPath); ?>
总结
如果追求用户体验(无需后端依赖),优先选择方案1前端直接生成WAV;如果必须用MediaRecorder生成Ogg,再排查FFmpeg的路径、参数和权限问题来完成后端转换。
内容的提问来源于stack exchange,提问作者Awazleon
相关产品推荐
相关产品推荐

