如何用c++和WebRTC实现P2P视频通话? (原生API)

需显式创建PeerConnectionFactoryInterface并传入网络、音频、视频三工厂,配置STUN服务器,手动采集摄像头帧并校准时间戳,逐个处理ICE候选者,实现VideoSinkInterface渲染。

直接用 C++ 原生 WebRTC API 实现 P2P 视频通话,不是加个 SDK 就完事——你得亲手管理 PeerConnectionMediaStreamVideoTrack、信令通道和 NAT 穿透逻辑。没有封装层兜底,出错时堆栈里全是 webrtc:: 命名空间的调用,调试门槛高,但可控性也最强。

怎么初始化 WebRTC 环境并创建 PeerConnection

WebRTC C++ 不提供单例或全局初始化入口,必须显式创建 webrtc::PeerConnectionFactoryInterface,它依赖三个核心工厂对象:网络、音频、视频。

漏掉任一依赖(比如没传 rtc::Thread 给网络层),CreatePeerConnection 会静默返回空指针。

常见错误是复用主线程做网络 IO —— WebRTC 要求网络线程独立,否则 SetLocalDescription 卡死或回调不触发。

  • 必须用 rtc::Thread::Create() 创建专用网络线程,并在 PeerConnectionFactoryDependencies 中传入
  • cricket::FakeAudioDeviceModule 可用于无音频场景,但视频必须配真实 webrtc::VideoEncoderFactorywebrtc::VideoDecoderFactory(如 webrtc::InternalEncoderFactory
  • PeerConnectionInterface::RTCConfiguration 里至少填一个 webrtc::PeerConnectionInterface::IceServer(哪怕只是 {"stun:stun.l.google.com:19302"}),否则 ICE 连接永远卡在 new

如何采集本地摄像头并绑定到 PeerConnection

C++ 版没有 getUserMedia 这种甜点 API,视频采集靠平台相关实现:Linux 用 webrtc::VideoCaptureModule(V4L2)、Windows 用 webrtc::DesktopCapturer 或第三方封装、macOS 需 AVFoundation 桥接。采集器启动后,要手动把帧送进 webrtc::VideoTrackSourceOnFrame 回调。

容易被忽略的是线程安全:采集线程和 WebRTC 内部视频处理线程不同,必须用 rtc::scoped_refptr 包裹 webrtc::VideoTrackSource,且帧时间戳需用 rtc::TimeMicros() 校准,否则远端看到卡顿或花屏。

  • 创建 webrtc::VideoTrackSource 时传 is_screencast = false,否则编码器可能跳过关键帧逻辑
  • 调用 pc->AddTrack(video_track, {kStreamId}) 后,必须立刻调用 pc->CreateOffer,否则 track 不会参与 SDP 生成
  • SDP 中的 video m-line 必须含 a=sendrecv,否则对方收不到视频流

为什么 setRemoteDescription 后 ICE 连接一直 failed

绝大多数失败不是因为代码写错,而是信令交换不完整或 ICE 候选者没发全。C++ API 要求你手动监听 OnIceCandidate,把每个 webrtc::IceCandidateInterface 序列化成字符串(用 candidate->ToString()),再通过信令通道发给对端;同样,收到对端候选者后,必须逐个调用 pc->AddIceCandidate,不能攒一批再加。

典型陷阱:OnIceCandidate 可能在 SetLocalDescription 前就触发(尤其 STUN 延迟低时),此时若信令通道未就绪,候选者就丢了。正确做法是缓存候选者列表,等信令通道 ready 后批量发送。

  • webrtc::PeerConnectionInterface::IceConnectionStatekIceConnectionFailed 时,检查日志里是否有 "Failed to connect to STUN server""No host candidates gathered"
  • pc->GetStatsRTCIceCandidatePairStatsstate 字段,确认是否走到 succeeded
  • 强制走 relay 时,RTCConfigurationtype 设为 cricket::RELAY_PORT_TYPE,并确保 TURN 凭据有效(username/password 过期会导致 ice_connection_state 卡在 checking

如何让远端视频渲染出来

WebRTC C++ 不提供内置播放器,你得自己实现 webrtc::VideoSinkInterface<:videoframe>,并在 OnFrame 里把 webrtc::VideoFramevideo_frame_buffer() 转成 OpenGL / Vulkan / Direct3D 可用纹理。缓冲区格式通常是 I420 或 NV12,frame.buffer()->ToI420() 会触发拷贝,性能敏感场景应直接读原始 rtc::scoped_refptr<:videoframebuffer>

另一个坑是线程:渲染回调默认在 WebRTC 的 worker_thread 上触发,若你的渲染循环在主线程,必须用 PostTask 跨线程投递帧数据,否则 OpenGL 上下文错乱或崩溃。

  • 注册 sink 用 video_track->AddSink(your_sink),不是 pc->AddSink
  • 确保 your_sink 生命周期长于 video_track,否则 AddSink 后立即析构 sink 会 crash
  • 测试阶段可用 webrtc::test::FakeVideoRenderer 替代自研渲染器,验证是否为渲染逻辑问题

最麻烦的从来不是编译通过,而是 ICE candidate 发送顺序错乱、视频帧时间戳跳变、或某条 AddIceCandidate 调用因线程竞争被丢弃——这些都不会抛异常,只表现为黑屏、单向通话、或连接秒断。建议从最小闭环(STUN + 本地回环)开始,逐项打开功能,用 pc->GetStats 和日志里的 webrtc::Logging 输出交叉验证每一步状态。