1 前言
当前基于sfu服务的webrtc通信模式下,服务端录像和服务端旁路引流,是后端服务的一个重要需求。
本文介绍webrtc服务端如何进行录像,文章通过几部分进行讨论:
-
mediasoup sfu的旁路录像
-
srs webrtc的旁路录像
-
webrtc录像的格式讨论
2 mediasoup webrtc的旁路录像
基本的架构图如下:

如上图,这里主要讨论,如何建立一个mediasoup mcu服务,从sfu中拉取用户A和用户B的音视频流,并且录像后,上传到对应的文件存储系统(如阿里云,腾讯云等的存储系统)。
这里介绍开源cpp_streamer通过mediasoup webrtc拉流组件,拉取音频(opus)+视频流(H264 baseline),并存储成mpegts格式。
cpp_streamer开源自己构造一个简单的PeerConnection类,实现:
-
sdp 信息解析
-
stun 协商
-
dtls 协商
-
rtp/rtcp报文传输
-
自定义快速jitterbuffer的实现
-
H264 in rtp的组装/解封装
通过以上的功能,构造mspull组件,也就是mediasoup pull拉流组件,实现webrtc的拉流,过内部jitterbuffer组装后,输出H264和opus。
这里录像采用的格式是mpegts,选取的原因是mpegts支持opus音频编码的封装。
2.1 代码实现
基于cpp_streamer的代码实现,其实比较简单,只需要3个组件,并且把他们联结在一起:
-
mspull: mediasoup拉流组件,把对应的音视频流拉取过来;
-
timesync: 音视频时间同步组件,因为webrtc的音视频流,rtp中的时间戳是完全不同步的。timesync组件帮助实现对音视频时间戳进行更新,让其同步;
-
mpegtsmux: mpegts封装组件,将拉取的音视频流(H264+Opus)封装成mpegts格式;

demo代码在: https://github.com/runner365/cpp_streamer/blob/v1.1/src/tools/mediasoup_pull_demo.cpp
cpp
class MsPull2MpegtsStreamerMgr: public StreamerReport, public CppStreamerInterface
{
public:
//src_url: 为mediasoup sfu对应音视频的信息地址
// "https://xxxxx.com?roomId=100&userId=1000&vpid=xxxxx&apid=xxxx"
// 其中roomId: 房间id; vpid是video的producerId; apid是audio的producerId
//output_ts: 要存的输出文件
MsPull2MpegtsStreamerMgr(const std::string& src_url,
const std::string& output_ts):ts_file_(output_ts)
, src_url_(src_url)
{
}
virtual ~MsPull2MpegtsStreamerMgr()
{
mspull_ready_ = false;
}
int MakeStreamers(uv_loop_t* loop_handle) {
loop_ = loop_handle;
//构造一个mspull组件,拉去mediasoup sfu的音视频流
mspull_streamer_ = CppStreamerFactory::MakeStreamer("mspull");
mspull_streamer_->SetLogger(logger_);
mspull_streamer_->SetReporter(this);
//构造一个mpegts封装组件,将音视频流封装成mpegts格式
ts_streamer_ = CppStreamerFactory::MakeStreamer("mpegtsmux");
ts_streamer_->SetLogger(logger_);
ts_streamer_->SetReporter(this);
//构造一个timesync组件,实现音频与视频时间戳的同步
timesync_streamer_ = CppStreamerFactory::MakeStreamer("timesync");
timesync_streamer_->SetLogger(logger_);
timesync_streamer_->SetReporter(this);
//将mspull组件,timersync组件,mpegtsmux组件联结在一起
// mspull--->timesync--->mpegtsmux--->this(存储mpegts成文件)
mspull_streamer_->AddSinker(timesync_streamer_);
timesync_streamer_->AddSinker(ts_streamer_);
ts_streamer_->AddSinker(this);
return 0;
}
//接收mpegtsmux输出的mpegt数据,存成mpegts的文件。
virtual int SourceData(Media_Packet_Ptr pkt_ptr) override {
FILE* file_p = fopen(ts_file_.c_str(), "ab+");
if (file_p) {
fwrite(pkt_ptr->buffer_ptr_->Data(), 1, pkt_ptr->buffer_ptr_->DataLen(), file_p);
fclose(file_p);
}
return 0;
}
}
3 Srs的旁路录像
Srs是强大的开源多媒体流服务器,支持webrtc sfu功能,并且还支持rtc2rtmp的功能(opus转码成aac),opus转码成aac对cpu的消耗不小,一个转码大概消耗2%左右的cpu。
如果用户接入路数不多,不考虑性能问题,也直接考虑用Srs配置rtc2rtmp,配置hls的录像,直接用srs实现录像功能。
本文主要考虑性能问题,介绍旁路录像的方案,也就是启动一个服务,从Srs拉取webrtc的音视频流,并保存成mpegts文件。
Srs支持Whep标准的webrtc拉流接口,这里cpp_streamer实现对应的whep拉流组件,对其拉流获取音视频,并通过mpegtsmux组件实现mpegts对音视频流(H264+Opus)的封装。
3.1 代码实现
基于cpp_streamer的代码实现,其实比较简单,只需要3个组件,并且把他们联结在一起:
-
whep: whep拉流组件,把对应srs webrtc的音视频流拉取过来;
-
mpegtsmux: mpegts封装组件,将拉取的音视频流(H264+Opus)封装成mpegts格式;

demo代码地址:
https://github.com/runner365/cpp_streamer/blob/v1.1/src/tools/whep_srs_demo.cpp
cpp
class Whep2MpegtsStreamerMgr: public StreamerReport, public CppStreamerInterface
{
public:
//src_url: srs的whep地址
// eg: "http://10.0.8.5:1985/rtc/v1/whip-play/?app=live&stream=1000"
//output_ts: 录像文件,mpegts格式
Whep2MpegtsStreamerMgr(const std::string& src_url,
const std::string& output_ts):ts_file_(output_ts)
, src_url_(src_url)
{
}
virtual ~Whep2MpegtsStreamerMgr()
{
whep_ready_ = false;
}
int MakeStreamers(uv_loop_t* loop_handle) {
loop_ = loop_handle;
//whep组件:从srs拉流webrtc音视频流
whep_streamer_ = CppStreamerFactory::MakeStreamer("whep");
whep_streamer_->SetLogger(logger_);
whep_streamer_->SetReporter(this);
//mpgetsmux组件: 把H264+Opus保存为mpegts格式
ts_streamer_ = CppStreamerFactory::MakeStreamer("mpegtsmux");
ts_streamer_->SetLogger(logger_);
ts_streamer_->SetReporter(this);
//联结whep组件和mpegtsmux组件,
// whep-->mpegtsmux-->this(保存mpegts文件)
whep_streamer_->AddSinker(ts_streamer_);
ts_streamer_->AddSinker(this);
return 0;
}
//保存mpegts文件
virtual int SourceData(Media_Packet_Ptr pkt_ptr) override {
FILE* file_p = fopen(ts_file_.c_str(), "ab+");
if (file_p) {
fwrite(pkt_ptr->buffer_ptr_->Data(), 1, pkt_ptr->buffer_ptr_->DataLen(), file_p);
fclose(file_p);
}
return 0;
}
}
4 录像格式讨论
本文的录像demo都采用mpegts,而且保存的编码格式都是H264+Opus.
并没有支持视频编码: Vp8/Vp9/Av1。
这里讨论如果做兼容各种编码的录像,在不转码的情况下(视频转码非常消耗服务器性能),采用什么封装格式比较推荐。
当前支持codec类型较多的封装格式有: Mp4, Mkv。
都能支持H264/H265/Vp8/Vp9/Av1。
4.1 Mp4(fMp4)
对于流媒体格式,Mp4不能很好的支持动态连续的录像,不过fMp4格式是能支持动态的持续的添加音视频流。
今后的文章会专门讨论fMp4用了哪些mp4 box来支持动态持续添加音视频流,专门做一期详解。
4.2 Mkv
MKV(Matroska Video File)是一种Matroska媒体格式的多媒体封装格式(Multimedia Container Format,简称MCF)。Matroska来自于俄语,影射俄罗斯娃娃,就是下面这个啦,表示一层包着另外一层。
可以支持动态持续添加音视频流,并且支持H264/H265/Vp8/Vp9/Av1。
今后的文章会专门介绍Mkv的格式封装,MKV采用可扩展二进制元语言EBML(Extensible Binary Meta Language)来描述其文件结构,EBML用元素(Elements)来描述EBML文档。
今后cpp_streamer会加入,mp4(fmp4), mkv等流行封装的组件。
后面会专门有后续博文介绍:
-
mp4/fmp4格式详解
-
mkv格式详解