RecordRTC + PHP + FFmpeg:WebRTC 音视频分离录制与 WebM 服务端合并实战指南
【免费下载链接】WebRTC-Experiment
WebRTC, WebRTC and WebRTC. Everything here is all about WebRTC!!
导读
本文基于 RecordRTC 仓库中的 PHP-and-FFmpeg 演示项目,完整讲解一条“浏览器端分别录制音频与视频 → 通过 HTTP 上传两个独立的 Blob → 由 PHP 服务端调用 FFmpeg 将二者合并为单个 WebM 文件”的端到端链路。读完本文,你将掌握 RecordRTC 双录制器协同(音频启动后触发视频录制)的时序控制技巧、FormData + XHR 上传 Blob 的标准写法、PHP 端接收与落盘流程,以及 FFmpeg 在 Linux / Windows 两种平台下的音视频合并命令差异,并理解该方案为什么能够支持尽可能长时间的录制。
一、项目背景:为什么需要“分离录制 + 服务端合并”
浏览器 WebRTC 录制通常有两条路:
- 单流录制:直接把包含音轨和视频轨的 MediaStream 交给一个
MediaRecorder,产出单一文件。这种方式实现简单,但录制时长、编码器与容器格式高度依赖浏览器实现(例如旧版 Chrome 的 MediaRecorder 视频默认只产出 WebM/VP8)。 - 分离录制:如 PHP-and-FFmpeg/index.html 所示,把音轨和视频轨拆成两条独立的流,分别用
StereoAudioRecorder(产出 WAV/PCM)与视频录制器(产出 WebM)录制,再把两个 Blob 上传到服务端,交给 FFmpeg 精确控制合并与封装。
该演示项目的 README.md 明确列出了它的四大能力,这也是本文要逐一展开的核心骨架:
- 同时录制音频与视频(audio + video);
- 使用 FFmpeg 在服务端将两者合并为单个 WebM 容器;
- 通过 FFmpeg 命令保证音频与视频同步;
- 支持尽可能长的录制时长。
“最长录制支持”的关键在于:分离录制时,视频端使用基于 Canvas 抓帧的 WhammyRecorder,它不依赖浏览器 MediaRecorder 的内存缓冲上限,配合本页在 onAudioProcessStarted 后才启动视频录制的时序设计,可以绕开单录制器长时间录制容易触发的内存与时长瓶颈。
二、端到端工作流总览
浏览器端 服务端(PHP)
──────────── ────────────
getUserMedia({audio,video})
│
├─ 音频流 → RecordRTC(StereoAudioRecorder)
│ └─ onAudioProcessStarted ──▶ 启动视频录制
├─ 仅视频轨的 MediaStream → RecordRTC(WhammyRecorder)
│
└─ Stop 后依次取回两个 Blob
│ FormData(filename / audio-blob / video-blob)
▼
XHR POST ──────────────────────────▶ save.php
│ move_uploaded_file
│ uploads/<id>.wav
│ uploads/<id>.webm
▼
exec('ffmpeg -i ...')
│
▼
uploads/<id>-merged.webm
◀── 返回 ffmpeg 输出文本
preview.src = uploads/<id>-merged.webm
这条链路全部由 index.html 与 save.php 两个文件实现,无需任何第三方框架。
三、浏览器端:双录制器分离录制
3.1 启动录制:音频先启动,视频随后跟进
点击 Record 后,页面先通过 getUserMedia 获取同时包含音频与视频的 MediaStream,然后执行两条核心逻辑(见 index.html):
recordAudio = RecordRTC(stream, {
type: 'audio',
recorderType: StereoAudioRecorder,
onAudioProcessStarted: function() {
recordVideo.startRecording();
}
});
var videoOnlyStream = new MediaStream();
videoOnlyStream.addTrack(stream.getVideoTracks()[0]);
recordVideo = RecordRTC(videoOnlyStream, {
type: 'video'
// recorderType 默认为 WhammyRecorder(旧 Chrome)或 MediaStreamRecorder
});
recordAudio.startRecording();
这里有两个值得展开的工程细节:
(1)为什么音频要先启动并作为视频的“发令枪”?
在 dev/StereoAudioRecorder.js 中,onAudioProcessStarted 是音频录制器在音频处理真正开始后回调的钩子:
if (config.onAudioProcessStarted) {
config.onAudioProcessStarted();
}
把 recordVideo.startRecording() 放在该回调里,可以保证两条录制的起始时刻对齐:音频录制的首个音频处理周期触发视频录制,从而让最终合并时音画同步有一个可靠的时间基准——这正是 README 中“audio is synced with video using ffmpeg commands”的浏览器端前置条件。
(2)为什么要构造“仅视频轨”的独立流?
stream.getVideoTracks()[0] 取出视频轨,放进新的 MediaStream,避免视频录制器同时拿到音频轨(否则可能产出含音轨的 WebM,与服务端合并逻辑冲突)。new MediaStream() + addTrack() 是标准的纯前端流构造方式。
3.2 停止录制:按顺序取回 Blob 并上传
点击 Stop 后,页面严格按“先音频、后视频”的顺序依次停止并取回 Blob(见 index.html):
fileName = Math.round(Math.random() * 99999999) + 99999999;
recordAudio.stopRecording(function() {
recordVideo.stopRecording(function() {
PostBlob(recordAudio.getBlob(), recordVideo.getBlob(), fileName);
});
});
recordAudio.getBlob()在StereoAudioRecorder下返回type: 'audio/wav'的 WAV Blob(源码见 dev/StereoAudioRecorder.js);recordVideo.getBlob()返回video/webmBlob(WhammyRecorder的产物类型见 dev/WhammyRecorder.js);- 文件名用
Math.random()生成 8 位随机数字,避免服务端文件互相覆盖,也防止并发上传冲突。
PostBlob 将两个 Blob 与文件名打包成 FormData,用原生 XHR POST 到 save.php,并在回调里把服务端返回的 FFmpeg 输出渲染到页面标题,同时把合并产物 uploads/<fileName>-merged.webm 赋值给 <video> 预览并自动播放(index.html):
formData.append('filename', fileName);
formData.append('audio-blob', audioBlob);
formData.append('video-blob', videoBlob);
xhr('save.php', formData, function(ffmpeg_output) {
document.querySelector('h1').innerHTML = ffmpeg_output.replace(/\n/g, '<br />');
preview.src = 'uploads/' + fileName + '-merged.webm';
preview.play();
});
四、服务端:save.php 接收、落盘与 FFmpeg 合并
4.1 上传接收与文件落盘
save.php 首先利用 $_FILES["audio-blob"] / $_FILES["video-blob"] 判断是否收到对应字段,并通过 move_uploaded_file 把临时文件分别写入:
uploads/<filename>.wav(音频);uploads/<filename>.webm(视频)。
只有当音频、视频都成功落盘后,才进入合并阶段。任一步失败都会输出“Problem writing … file to disk!”提示,便于前端排查。
4.2 平台识别:Linux 与 Windows 采用不同合并命令
由于 Linux 与 Windows 的 FFmpeg 封装能力存在差异,save.php 通过 HTTP_USER_AGENT 正则匹配识别当前服务端操作系统($OSList 数组覆盖 Windows、Linux、Mac OS、BSD 等十余种 UA 模式),再选择不同的命令:
if(!strrpos($CurrOS, "Windows")) {
$cmd = '-i '.$audioFile.' -i '.$videoFile.' -map 0:0 -map 1:0 '.$mergedFile;
} else {
$cmd = ' -i '.$audioFile.' -i '.$videoFile.' -c:v mpeg4 -c:a vorbis -b:v 64k -b:a 12k -strict experimental '.$mergedFile;
}
exec('ffmpeg '.$cmd.' 2>&1', $out, $ret);
- Linux 分支:
-map 0:0 -map 1:0分别选取输入 0(音频 WAV)的 0 号流与输入 1(视频 WebM)的 0 号流,直接复用源流的编码格式(Vorbis 音频 + VP8 视频)封装进单个 WebM 容器,质量无损、速度最快,因此被注释明确建议在 Linux 服务器上部署。 - Windows 分支:显式指定
-c:v mpeg4 -c:a vorbis并设置-b:v 64k -b:a 12k码率,同时需要-strict experimental(旧版 FFmpeg 启用实验性 Vorbis 编码器所需的开关)。这也解释了文件头部的提示——“make sure that you're using newest ffmpeg version”,新版 FFmpeg 可去掉该实验性标志。
两个分支都将 2>&1 重定向 FFmpeg 的标准错误输出,使 exec() 的 $out 能捕获完整日志,供前端展示排错。
4.3 合并成功后的清理与回执
if ($ret){
echo "There was a problem!\n";
print_r($cmd.'\n');
print_r($out);
} else {
echo "Ffmpeg successfully merged audio/video files into single WebM container!\n";
unlink($audioFile);
unlink($videoFile);
}
$ret为 FFmpeg 进程退出码,非 0 表示失败,输出命令原文与完整日志;- 成功时输出合并成功提示,并
unlink删除两个中间文件,只保留uploads/<filename>-merged.webm供浏览器回放。该目录在仓库中预置了 uploads/README.md,用于存放上传产物,部署时需保证该目录对 PHP 进程可写。
五、依赖与运行前置条件
| 环节 | 依赖 | 说明 |
|---|---|---|
| 浏览器端 | RecordRTC.js(页面从 CDN 加载,源码见 RecordRTC.js) | 内含 StereoAudioRecorder(dev/StereoAudioRecorder.js)、WhammyRecorder(dev/WhammyRecorder.js)等模块 |
| 音频产物 | WAV/PCM | 由 StereoAudioRecorder 前端直接生成,无需转码 |
| 视频产物 | WebM | WhammyRecorder 将 Canvas 帧编码为 WebM(dev/WhammyRecorder.js) |
| 服务端 | PHP(支持 move_uploaded_file、exec)+ FFmpeg | 依赖链:ffmpeg 依赖 yasm,libvpx 依赖 libvorbis,libvorbis 依赖 libogg(见 save.php 注释) |
部署时需注意:
uploads/目录必须对 PHP 进程可写(move_uploaded_file与最终合并文件都写入该目录);- FFmpeg 必须加入 PATH 环境变量,
exec('ffmpeg ...')才能直接调用; - 建议使用最新版 FFmpeg(尤其 Windows 分支依赖
-strict experimental的历史行为); - 页面以
<video id="preview">预览直播画面,录制期间muted = true防止回声;停止后用ajax-loader.gif作为加载占位(index.html)。
六、与仓库内其他方案的关系
- RecordRTC-to-PHP 演示的是“单 Blob 直接 POST 保存到 PHP”的简单场景,不涉及合并;
- RecordRTC-over-Socketio 走 Socket.io 通道传输录制文件,适合实时流式上传;
- 本演示是仓库中唯一同时覆盖“双录制器分离 + 服务端 FFmpeg 合并”的完整示例,属于 RecordRTC 在“如何获取最长录制时长 + 精确控制封装格式”方向上的参考实现。更广泛的录制场景(Canvas 录制、WebGL 录制、MRecordRTC 多流合并等)可参见 RecordRTC/dev/README.md。
七、小结
RecordRTC / PHP / FFmpeg 演示给出了一套生产可借鉴的分层思路:前端负责采集与分离、服务端负责合并与封装。核心要点可总结为:
- 分离录制:音频走
StereoAudioRecorder(WAV),视频走基于 Canvas 的WhammyRecorder(WebM),避免单录制器长时间录制的缓冲瓶颈; - 时序同步:利用
onAudioProcessStarted回调延迟启动视频录制,为 FFmpeg 合并提供同步基准; - 双平台命令:Linux 用
-map无损复用源流,Windows 用显式编码参数 +-strict experimental; - 资源回收:合并成功后删除中间文件,只保留最终 WebM。
按照本文的链路,将 index.html 与 save.php 放到支持 PHP + FFmpeg 的服务器上,即可获得一个可运行的“长时录音录屏 + 服务端合成”最小示例。
【免费下载链接】WebRTC-Experiment
WebRTC, WebRTC and WebRTC. Everything here is all about WebRTC!!
更多推荐
所有评论(0)