
本文旨在解决使用Javascript MediaRecorder进行实时录音,并通过base64编码传输至PHP服务器保存为`.ogg`文件时,文件损坏无法播放的问题。核心问题在于`MediaRecorder`的媒体类型配置不当,以及服务器端对音频数据块的处理方式错误(覆盖而非追加)。教程将详细阐述正确的客户端配置和服务器端文件追加策略,并提供完整的代码示例。
1. 理解MediaRecorder与音频数据流
MediaRecorder API允许我们录制用户的音频和视频流。它通过ondataavailable事件周期性地提供媒体数据块(e.data),这些数据块通常是媒体流的一部分,而非完整的、可独立播放的文件。为了将这些数据块组合成一个可播放的媒体文件,我们需要在客户端或服务器端进行适当的处理。
在将数据发送到服务器进行保存时,常见的流程是:
MediaRecorder捕获音频数据。ondataavailable事件触发,提供一个数据块。数据块被编码(例如base64),并通过HTTP请求发送到服务器。服务器接收数据,解码,并保存到文件。然而,在这个过程中,有两个关键环节容易导致最终文件损坏。
立即学习“PHP免费学习笔记(深入)”;
2. 客户端配置问题:错误的媒体类型指定
原始代码中,开发者尝试在创建Blob对象时指定媒体类型:
const blob = new Blob(chunks, { 'type' : 'audio/ogg; codecs=opus' });登录后复制这种做法是错误的。MediaRecorder在开始录制时,就需要知道它应该以何种格式和编码器来处理媒体流。Blob构造函数中的type参数仅用于标识Blob的MIME类型,并不会改变其内部数据的实际编码格式。
正确做法是,在MediaRecorder的构造函数中指定媒体类型和编码器。这样,MediaRecorder才会按照指定的格式生成e.data数据块。
// ...navigator.mediaDevices.getUserMedia ({ audio: true }) .then(function(stream) { // 在这里定义 MediaRecorder 的选项 const mrOptions = { mimeType: 'audio/ogg; codecs=opus' }; mediaRecorder = new MediaRecorder(stream, mrOptions); // 将选项传递给构造函数 mediaRecorder.start(2000); // 每2秒触发一次 ondataavailable 事件 mediaRecorder.ondataavailable = function(e) { chunks.push(e.data); // 创建 Blob 时,可以引用 MediaRecorder 实际使用的 mimeType const blob = new Blob(chunks, { type : mediaRecorder.mimeType }); chunks = []; // 清空 chunks,准备接收下一个数据块 // ... 后续处理 }; })// ...登录后复制通过在MediaRecorder构造函数中设置mimeType,我们确保了e.data数据块本身就是以audio/ogg; codecs=opus格式编码的。
LobeHub LobeChat brings you the best user experience of ChatGPT, OLLaMA, Gemini, Claude
302 查看详情
3. 服务器端处理问题:文件覆盖而非追加
原始PHP代码使用file_put_contents("r.ogg", base64_decode($_POST["data"]));来保存数据。file_put_contents函数在默认情况下会覆盖目标文件的全部内容。由于MediaRecorder会周期性地发送数据块,每次服务器收到数据时都会覆盖之前的内容,导致最终文件只包含最后一个数据块,这显然不是一个完整的、可播放的音频文件。
要解决这个问题,我们需要将每次收到的数据追加到文件中,而不是覆盖。
<?phpif(isset($_POST["data"])){ // 使用 FILE_APPEND 标志,将数据追加到文件末尾 file_put_contents("r.ogg", base64_decode($_POST["data"]), FILE_APPEND); exit;}?>登录后复制重要注意事项:虽然使用FILE_APPEND可以解决文件覆盖问题,但简单地将原始的Ogg Opus数据块追加到文件中,不一定能保证生成一个完全符合Ogg容器规范的、可播放的.ogg文件。Ogg文件格式包含复杂的页结构、头部信息和数据流管理。MediaRecorder生成的e.data块可能只是原始的Opus编码数据,或者是不完整的Ogg页。直接拼接这些块可能会导致文件结构损坏,或者播放器无法正确解析。
对于更健壮的实时流媒体保存方案,通常需要:
客户端一次性发送完整Blob: 在录制结束时,将所有e.data块聚合成一个大的Blob,然后一次性发送到服务器。这是最简单且可靠的方法,因为MediaRecorder能够确保最终的Blob是一个有效的媒体文件。服务器端流媒体处理: 使用专门的媒体处理库或服务来接收并正确地拼接或封装这些数据块,以构建一个有效的Ogg容器。这超出了简单的file_put_contents功能。然而,对于本教程的目标——修复用户现有代码中的直接问题,将file_put_contents改为追加操作是首要且必要的步骤。如果即使追加后文件仍然无法完美播放,则需要考虑上述更复杂的流媒体处理方案。
4. 完整代码示例
结合上述两点修正,以下是优化后的客户端Javascript和服务器端PHP代码:
客户端 Javascript (JS)
<script>var mediaRecorder = null;let chunks = [];if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { console.log('getUserMedia supported.'); navigator.mediaDevices.getUserMedia ( { audio: true }) .then(function(stream) { // 1. 在 MediaRecorder 构造函数中指定媒体类型和编码器 const mrOptions = { mimeType: 'audio/ogg; codecs=opus' }; mediaRecorder = new MediaRecorder(stream, mrOptions); // 每2秒触发一次 ondataavailable 事件,发送数据块 mediaRecorder.start(2000); mediaRecorder.ondataavailable = function(e) { chunks.push(e.data); // 2. 创建 Blob 时,使用 MediaRecorder 实际的 mimeType const blob = new Blob(chunks, { type : mediaRecorder.mimeType }); chunks = []; // 清空 chunks,准备接收下一个数据块 var reader = new FileReader(); reader.readAsDataURL(blob); reader.onloadend = function() { var data = reader.result.split(";base64,")[1]; requestp2("a.php", "data="+encodeURIComponent(data)); } }; // 可以添加停止录音的逻辑,例如: // setTimeout(() => { // mediaRecorder.stop(); // console.log('Recording stopped.'); // }, 10000); // 录制10秒后停止 }) .catch(function(err) { console.log('The following getUserMedia error occurred: ' + err); });} else { console.log('getUserMedia not supported on your browser!');}function requestp2(path, data){ var http = new XMLHttpRequest(); http.open('POST', path, true); http.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); http.send(data);}</script>登录后复制服务器端 PHP
将此代码保存为 a.php (或您在 requestp2 函数中指定的任何文件名)。
<?phpif(isset($_POST["data"])){ // 1. 解码 base64 数据 $decodedData = base64_decode($_POST["data"]); // 2. 将数据追加到文件末尾,而不是覆盖 // 注意:首次写入时,如果文件不存在,file_put_contents 会创建它。 // 每次后续写入都会追加。 if ($decodedData !== false) { file_put_contents("r.ogg", $decodedData, FILE_APPEND); } else { error_log("Failed to decode base64 data."); } exit; }?>登录后复制5. 最佳实践与注意事项
文件完整性: 尽管上述修正解决了mimeType配置和文件覆盖问题,但对于Ogg Opus这类复杂容器格式,直接追加原始数据块可能仍无法保证生成一个100%完美的、所有播放器都能兼容的文件。最可靠的方法是在录制结束后,一次性将所有数据块合并成一个完整的Blob发送到服务器。错误处理: 在实际应用中,应加强错误处理,例如检查getUserMedia是否成功、base64_decode是否失败、file_put_contents是否成功等。MIME类型兼容性: 并非所有浏览器都支持所有mimeType和codecs组合。在选择格式时,应考虑目标用户的浏览器兼容性。audio/ogg; codecs=opus通常有较好的支持。服务器负载: 频繁发送小数据块可能会增加服务器的I/O和网络负载。根据应用需求,可以调整mediaRecorder.start()的间隔时间,或者在客户端积累更多数据后再发送。安全性: 从客户端接收并保存文件到服务器时,务必进行输入验证和清理,防止潜在的安全漏洞(例如路径遍历攻击)。本教程中的示例代码未包含此类安全措施,仅用于演示核心功能。总结
修复MediaRecorder实时录音至PHP保存文件损坏的问题,关键在于两点:首先,确保在MediaRecorder构造函数中正确指定媒体类型和编码器,使其生成符合预期格式的数据块;其次,在服务器端使用FILE_APPEND模式将接收到的数据块追加到文件中,而非覆盖。虽然直接追加可能对某些复杂媒体格式的完整性有局限,但它解决了文件损坏的核心原因,为进一步的媒体处理奠定了基础。
以上就是修复MediaRecorder实时录音至PHP保存文件损坏问题的详细内容,更多请关注php中文网其它相关文章!



