• 示例工程

【免费下载链接】WebRTC-Experiment

WebRTC, WebRTC and WebRTC. Everything here is all about WebRTC!!

项目地址: https://gitcode.com/gh_mirrors/we/WebRTC-Experiment
点击查看 免费下载

导读

本文基于 RecordRTC 仓库中的 PHP-and-FFmpeg 演示项目,完整讲解一条“浏览器端分别录制音频与视频 → 通过 HTTP 上传两个独立的 Blob → 由 PHP 服务端调用 FFmpeg 将二者合并为单个 WebM 文件”的端到端链路。读完本文,你将掌握 RecordRTC 双录制器协同(音频启动后触发视频录制)的时序控制技巧、FormData + XHR 上传 Blob 的标准写法、PHP 端接收与落盘流程,以及 FFmpeg 在 Linux / Windows 两种平台下的音视频合并命令差异,并理解该方案为什么能够支持尽可能长时间的录制。

一、项目背景:为什么需要“分离录制 + 服务端合并”

浏览器 WebRTC 录制通常有两条路:

  1. 单流录制:直接把包含音轨和视频轨的 MediaStream 交给一个 MediaRecorder,产出单一文件。这种方式实现简单,但录制时长、编码器与容器格式高度依赖浏览器实现(例如旧版 Chrome 的 MediaRecorder 视频默认只产出 WebM/VP8)。
  2. 分离录制:如 PHP-and-FFmpeg/index.html 所示,把音轨和视频轨拆成两条独立的流,分别用 StereoAudioRecorder(产出 WAV/PCM)与视频录制器(产出 WebM)录制,再把两个 Blob 上传到服务端,交给 FFmpeg 精确控制合并与封装。

该演示项目的 README.md 明确列出了它的四大能力,这也是本文要逐一展开的核心骨架:

  1. 同时录制音频与视频(audio + video);
  2. 使用 FFmpeg 在服务端将两者合并为单个 WebM 容器;
  3. 通过 FFmpeg 命令保证音频与视频同步;
  4. 支持尽可能长的录制时长。

“最长录制支持”的关键在于:分离录制时,视频端使用基于 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/webm Blob(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 前端直接生成,无需转码
视频产物WebMWhammyRecorder 将 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 演示给出了一套生产可借鉴的分层思路:前端负责采集与分离、服务端负责合并与封装。核心要点可总结为:

  1. 分离录制:音频走 StereoAudioRecorder(WAV),视频走基于 Canvas 的 WhammyRecorder(WebM),避免单录制器长时间录制的缓冲瓶颈;
  2. 时序同步:利用 onAudioProcessStarted 回调延迟启动视频录制,为 FFmpeg 合并提供同步基准;
  3. 双平台命令:Linux 用 -map 无损复用源流,Windows 用显式编码参数 + -strict experimental;
  4. 资源回收:合并成功后删除中间文件,只保留最终 WebM。

按照本文的链路,将 index.html 与 save.php 放到支持 PHP + FFmpeg 的服务器上,即可获得一个可运行的“长时录音录屏 + 服务端合成”最小示例。

  • 示例工程

【免费下载链接】WebRTC-Experiment

WebRTC, WebRTC and WebRTC. Everything here is all about WebRTC!!

项目地址: https://gitcode.com/gh_mirrors/we/WebRTC-Experiment
点击查看 免费下载
Logo

邀请您加入社区

更多推荐