metaRTC代码阅读一
P2P
WebRTC 的 P2P 连接,核心就是两个端点之间直接建立音视频数据通道,不经过中转服务器(除了信令和 NAT 穿透辅助)。
在 metaRTC 这套代码里,P2P 的实现主要分为以下几个步骤:
信令交换(SDP/ICE)
- 通过信令服务器(可以是 SRS、Janus、WHIP 等)交换SDP(会话描述)和ICE Candidate(候选地址)。
- 代码里相关的类有:
YangPeerConnection7、YangRtcPublish、YangMetaConnection等。
建立 ICE 连接
- 通过 ICE 协议,尝试各种网络路径(本地IP、公网IP、STUN/TURN服务器)建立点对点连接。
- 代码里
YangPeerConnection7负责 ICE 相关的回调和状态管理。
DTLS 握手
- 建立安全的传输通道,双方通过 DTLS(基于 UDP 的 TLS)完成密钥协商。
- 代码里会检查 DTLS 状态,只有握手完成后才会真正传输音视频数据。
SRTP/RTP 传输音视频
- 音视频数据经过编码(H264/H265/Opus等),打包成 RTP 包,通过 SRTP(加密的 RTP)直接在两个 peer 之间传输。
- 代码里
YangPeerConnection7、YangMetaConnection、YangRtcConnection、pushH264/pushH265等模块负责数据的打包和发送。
代码结构简化图
camera采集流程
采集线程的启动流程如下:
采集线程的本质
在 YangVideoCaptureLinux 这类采集类中,采集线程其实就是通过调用其 run() 方法启动的,而 run() 方法内部会调用 startLoop(),进入主循环不断采集摄像头数据。
采集线程的启动位置
采集线程的启动,通常是在上层通过 start() 方法或者直接新建线程来实现的。
在 metaRTC 这套代码里,采集线程的启动一般发生在如下流程:
-
YangPushCapture::startVideoCapture()
这个方法会判断m_videoCapture是否存在且未启动,然后调用m_videoCapture->start()。 -
YangVideoCapture及其子类(如YangVideoCaptureLinux)通常继承自某个线程基类(如YangThread或直接用 std::thread/QThread),其start()方法会新建线程并自动调用run()。
代码链路举例
以 Linux 为例,流程大致如下:
- 上层调用
YangPushCapture::startVideoCapture()if(m_videoCapture && !m_videoCapture->m_isStart) m_videoCapture->start(); - 线程启动
start()方法新建线程,线程入口是run()。 - 进入主循环
run()方法内部调用startLoop(),不断采集摄像头数据。
相关代码片段
-
YangPushCapture.cppvoid YangPushCapture::startVideoCapture(){ if(m_videoCapture && !m_videoCapture->m_isStart) m_videoCapture->start(); } -
YangVideoCapture.cppvoid YangVideoCapture::run() { m_isStart = 1; startLoop(); m_isStart = 0; }
采集线程的启动是通过 YangPushCapture::startVideoCapture() 间接调用 m_videoCapture->start() 实现的,start() 会新建线程并自动执行 run(),最终进入 startLoop() 进行摄像头数据的持续采集。
采集接口
startLoop
在 YangVideoCaptureLinux 里,startLoop() 的作用是:
启动摄像头采集主循环,不断从摄像头设备读取视频帧,并把帧数据写入缓冲区。
主要流程
- 初始化缓冲区:把所有 V4L2 的 buffer 都入队(
VIDIOC_QBUF)。 - 启动视频流:调用
VIDIOC_STREAMON开始采集。 - 主循环:用
select等待摄像头设备有新数据可读。 - 读取帧数据:每次有新数据,调用
read_buffer(),从设备取出一帧,并通过m_vhandle->putBuffer(...)写入YangVideoBuffer。 - 循环直到采集停止。
关键代码片段
void YangVideoCaptureLinux::startLoop() {
// 1. 入队所有缓冲区
// 2. 启动视频流
// 3. 主循环,不断 select + read_buffer
while (m_isloop) {
// 等待数据
r = select(m_vd_id + 1, &fds, NULL, NULL, &tv);
// 读取一帧
read_buffer();
}
}
startLoop 在哪里被调用?
- 直接在
YangVideoCapture的run()方法里被调用:
void YangVideoCapture::run() {
m_isStart = 1;
startLoop();
m_isStart = 0;
}
-
采集线程启动时会调用
run(),从而进入startLoop()。 -
采集线程的启动一般由上层的
startVideoCapture()或类似方法触发。
写入缓冲区
摄像头采集线程把每一帧视频数据写入 YangVideoBuffer(如 m_out_videoBuffer、m_pre_videoBuffer)的核心代码在 YangVideoCaptureHandle 这个类里,具体如下:
1. 采集线程调用 putBuffer 或相关方法
- 采集到一帧数据后,都会调用
YangVideoCaptureHandle::putBuffer或类似方法。
//libmetartc7/src/yangcapture/linux/YangVideoCaptureLinux.cpp
if (m_vhandle)
m_vhandle->putBuffer(m_timestatmp, m_user_buffer[m_buf.index].start, m_user_buffer[m_buf.index].length);
2. putBuffer 内部会调用 putBuffers
-
putBuffer负责处理格式转换、时间戳等,然后调用putBuffers:void YangVideoCaptureHandle::putBuffer(int64_t pstamtime, uint8_t *pBuffer, int32_t BufferLen) { // ... 省略格式转换 ... m_videoFrame.payload = tmp; m_videoFrame.nb = m_bufLen; putBuffers(); // ... }
3. putBuffers 把帧写入各个缓冲区
-
这里会把当前帧写入
m_pre_videoBuffer、m_out_videoBuffer等:void YangVideoCaptureHandle::putBuffers() { if(m_pre_videoBuffer) m_pre_videoBuffer->putVideo(&m_videoFrame); if(m_isFilm && m_film_out_videoBuffer) m_film_out_videoBuffer->putVideo(&m_videoFrame); if (m_isCapture && m_out_videoBuffer) { m_out_videoBuffer->putVideo(&m_videoFrame); } if (m_isLivingCaptrue && m_living_out_videoBuffer){ m_living_out_videoBuffer->putVideo(&m_videoFrame); } }
m_pre_videoBuffer、m_out_videoBufferbuffer 的作用
1. m_pre_videoBuffer
- 全称:预处理视频缓冲区(Pre Video Buffer)
- 作用:
- 主要用于本地预览,也就是把采集到的原始摄像头帧,直接显示在本地界面上(比如 Qt 窗口)。
- 有时也可以作为编码前的中转区,便于后续处理(如加水印、旋转等)。
- 典型用法:
- 在渲染线程(如
YangRecordThread::render())里,从m_pre_videoBuffer取出一帧,显示到本地窗口。 - 不参与网络推流。
- 在渲染线程(如
2. m_out_videoBuffer
- 全称:输出视频缓冲区(Out Video Buffer)
- 作用:
- 主要用于编码和推流,也就是把采集到的帧送给编码器,编码后再推送到远端。
- 这个 buffer 是编码器的输入源。
- 典型用法:
- 编码线程会不断从
m_out_videoBuffer取出帧,进行 H264/H265 等编码,然后编码后的数据再送到网络推流模块。 - 只参与推流,不直接用于本地预览。
- 编码线程会不断从
为什么有两个buffer?
- 解耦:本地预览和推流编码是两条独立的链路,互不影响。即使推流卡住,本地预览也能流畅显示。
- 效率:本地预览通常不需要编码,直接显示原始帧更快;推流则需要编码压缩,流程更复杂。
- 灵活性:可以在预处理 buffer 做一些本地特效、调试等,不影响推流。
4. 代码举例
- 写入(采集线程):
if(m_pre_videoBuffer) m_pre_videoBuffer->putVideo(&m_videoFrame); if(m_isCapture && m_out_videoBuffer) m_out_videoBuffer->putVideo(&m_videoFrame); - 读取(本地预览):
uint8_t* t_vb = m_pre_videoBuffer->getVideoRef(&m_frame); - 读取(编码推流):
uint8_t* t_vb = m_out_videoBuffer->getVideoRef(&m_frame);
m_pre_videoBuffer:本地预览用,让你能在界面上看到摄像头画面。m_out_videoBuffer:编码推流用,把视频帧送给编码器和网络,发给对方。
每一帧摄像头数据采集后,最终都是通过
YangVideoCaptureHandle::putBuffers()写入YangVideoBuffer,而putBuffers()会把帧分发到m_out_videoBuffer、m_pre_videoBuffer等缓冲区。
编码
编码线程从 m_out_videoBuffer 取帧并进行 H264/H265 编码的核心代码在 YangVideoEncoderHandle 里,具体如下:
1. 设置输入缓冲区
编码线程会通过 setInVideoBuffer 方法,把 m_out_videoBuffer 传进来:
void YangVideoEncoderHandle::setInVideoBuffer(YangVideoBuffer *plist) {
m_in_videoBuffer = plist;
}
2. 编码线程主循环
编码线程的主循环在 YangVideoEncoderHandle::startLoop():
void YangVideoEncoderHandle::startLoop() {
m_isConvert = 1;
// ... 省略参数准备 ...
while (m_isConvert == 1) {
if (m_in_videoBuffer->size() == 0) {
yang_usleep(20000);
continue;
}
uint8_t *tmpsrc = m_in_videoBuffer->getVideoRef(&videoFrame);
if(!tmpsrc) continue;
// ... 格式转换等 ...
t_Encoder->encode(&videoFrame, this); // 进行H264/H265编码
}
// ... 资源释放 ...
}
- 这里的
m_in_videoBuffer就是m_out_videoBuffer,即采集线程写入的 buffer。 getVideoRef(&videoFrame)从 buffer 取出一帧,填充到videoFrame结构体。
3. 编码实现
编码器对象(如 YangOpenH264Encoder、YangVideoEncoderFfmpeg 等)会把 videoFrame 编码成 H264/H265 数据。
例如软编码(OpenH264):
int32_t YangOpenH264Encoder::encode(YangFrame* pframe, YangEncoderCallback* pcallback) {
// ... 编码 ...
if (pcallback)
pcallback->onVideoData(pframe); // 编码后回调
}
- 采集线程:写入
m_out_videoBuffer - 编码线程:
startLoop()里不断getVideoRef()取帧,encode()编码 - 编码后的数据:通过回调/缓冲区传递给推流模块
`YangVideoEncoderHandle::startLoop()
YangVideoEncoderHandle::startLoop() 是在 YangVideoEncoderHandle::run() 里被调用的,而 run() 是编码线程的主函数。
YangPushPublish::startVideoEncoding()
→YangPushEncoder::startVideoEncoder()
→YangVideoEncoderHandle::start()
→ 新线程启动,自动调用YangVideoEncoderHandle::run()
→run()里调用startLoop(),开始编码循环
YangVideoEncoderHandle
pcallback->onVideoData(pframe); 这个回调的配置,其实就是编码线程本身,也就是 YangVideoEncoderHandle 这个对象。
在 YangVideoEncoderHandle::startLoop() 里有:
t_Encoder->encode(&videoFrame, this);
这里的 this 就是当前的 YangVideoEncoderHandle 对象。
YangVideoEncoderHandle 继承了 YangEncoderCallback(或者实现了 onVideoData 这个接口),所以可以作为回调传进去。
回调的作用
- 编码器编码完一帧后,调用
pcallback->onVideoData(pframe)。 - 实际上就是调用
YangVideoEncoderHandle::onVideoData(pframe)。 - 这个函数里会把编码后的帧写入编码后缓冲区(
m_out_videoBuffer->putEVideo(pframe)),供推流线程读取。
// YangVideoEncoderHandle.cpp
void YangVideoEncoderHandle::onVideoData(YangFrame* pframe) {
if (pframe->nb > 4) {
/*- 这是一个回调函数,当视频编码器(如 x264、FFmpeg 等)成功编码一帧视频后,就会调用这个函数。- 参数 pframe 包含了编码好的视频数据(比如 H264 NALU)*/
m_out_videoBuffer->putEVideo(pframe);
//- m_out_videoBuffer 是YangPushEncoder类的m_out_videoBuffer
}
}
pcallback->onVideoData(pframe) 的回调对象就是 YangVideoEncoderHandle,也就是编码线程自己,编码线程通过实现回调接口来接收编码后的数据。
推流
推流线程/模块读取编码帧
- 推流核心类(如
YangRtcPublish)会有一个循环,不断从YangVideoEncoderBuffer(也就是m_out_videoBuffer)里取出编码好的帧。 - 取到编码帧后,推流模块会把数据打包成 RTP/WHIP/WebRTC 等协议包,最终通过 UDP/TCP 发送到远端。
on_video函数的具体定义位于YangRtcPublish类中,文件路径为demo/metapushstream7/yangpush/YangRtcPublish.cpp。以下是该函数的相关代码片段:
void YangRtcPublish::startLoop() {
// ... 省略其他代码 ...
while (m_isConvert == 1) {
// ... 省略其他代码 ...
if (m_in_videoBuffer && m_in_videoBuffer->size() > 0) {
videoFrame.payload = m_in_videoBuffer->getEVideoRef(&videoFrame);
if (videoFrame.frametype == YANG_Frametype_I) {
// ... 处理元数据 ...
data.setVideoFrametype(data.context, YANG_Frametype_Spspps);
data.setMetaTimestamp(data.context, videoFrame.pts);
ret = stream->on_video(data.getVideoFrame(data.context));
// ... 处理视频帧 ...
}
data.setVideoData(data.context, &videoFrame, videoType);
ret = stream->on_video(data.getVideoFrame(data.context));
// ... 处理连接状态 ...
}
}
// ... 省略其他代码 ...
}
on_video函数的调用发生在startLoop方法中,主要用于将视频帧发送到远程对等方。- 该函数会检查视频缓冲区是否有可用的帧,并处理这些帧的元数据,然后通过
stream->on_video方法将其发送出去。 - stream就是YangPeerConnection7的类指针
int32_t YangPeerConnection7::on_video(YangFrame* videoFrame){
return m_conn.on_video(&m_peer, videoFrame);
//`m_conn` 是 `YangMetaConnection` 这个**结构体**的对象。
}
YangMetaConnection
m_conn 是 YangMetaConnection 这个结构体的对象。
在 YangPeerConnection7 的声明(include/yangrtc/YangPeerConnection7.h)里:
class YangPeerConnection7 {
// ...
private:
YangMetaConnection m_conn;
// ...
};
YangMetaConnection 是一个结构体(C风格),定义在 include/yangrtc/YangMetaConnection.h:
typedef struct {
// 一堆函数指针,比如
int32_t (*on_video)(YangPeer* peer,YangFrame* videoFrame);
// ...
} YangMetaConnection;
它本质上是一个函数指针表,类似于接口或虚表,里面的函数指针在初始化时会被赋值为具体的实现函数。
更多推荐



所有评论(0)