需显式创建PeerConnectionFactoryInterface并传入网络、音频、视频三工厂,配置STUN服务器,手动采集摄像头帧并校准时间戳,逐个处理ICE候选者,实现VideoSinkInterface渲染。
直接用 C++ 原生 WebRTC API 实现 P2P 视频通话,不是加个 SDK 就完事——你得亲手管理 PeerConnection、MediaStream、VideoTrack、信令通道和 NAT 穿透逻辑。没有封装层兜底,出错时堆栈里全是 webrtc:: 命名空间的调用,调试门槛高,但可控性也最强。
WebRTC C++ 不提供单例或全局初始化入口,必须显式创建 webrtc::PeerConnectionFactoryInterface,它依赖三个核心工厂对象:网络、音频、视频。漏掉任一依赖(比如没传 rtc::Thread 给网络层),CreatePeerConnection 会静默返回空指针。
常见错误是复用主线程做网络 IO —— WebRTC 要求网络线程独立,否则 SetLocalDescription 卡死或回调不触发。
rtc::Thread::Create() 创建专用网络线程,并在 PeerConnectionFactoryDependencies 中传入cricket::FakeAudioDeviceModule 可用于无音频场景,但视频必须配真实 webrtc::VideoEncoderFactory 和 webrtc::VideoDecoderFactory(如 webrtc::InternalEncoderFactory)PeerConnectionInterface::RTCConfiguration 里至少填一个 webrtc::PeerConnectionInterface::IceServer(哪怕只是 {"stun:stun.l.google.com:19302"}),否则 ICE 连接永远卡在 new
C++ 版没有 getUserMedia 这种甜点 API,视频采集靠平台相关实现:Linux 用 webrtc::VideoCaptureModule(V4L2)、Windows 用 webrtc::DesktopCapturer 或第三方封装、macOS 需 AVFoundation 桥接。采集器启动后,要手动把帧送进 webrtc::VideoTrackSource 的 OnFrame 回调。
容易被忽略的是线程安全:采集线程和 WebRTC 内部视频处理线程不同,必须用 rtc::scoped_refptr 包裹 webrtc::VideoTrackSource,且帧时间戳需用 rtc::TimeMicros() 校准,否则远端看到卡顿或花屏。
webrtc::VideoTrackSource 时传 is_screencast = false,否则编码器可能跳过关键帧逻辑pc->AddTrack(video_track, {kStreamId}) 后,必须立刻调用 pc->CreateOffer,否则 track 不会参与 SDP 生成video m-line 必须含 a=sendrecv,否则对方收不到视频流绝大多数失败不是因为代码写错,而是信令交换不完整或 ICE 候选者没发全。C++ API 要求你手动监听 OnIceCandidate,把每个 webrtc::IceCandidateInterface 序列化成字符串(用 candidate->ToString()),再通过信令通道发给对端;同样,收到对端候选者后,必须逐个调用 pc->AddIceCandidate,不能攒一批再加。
典型陷阱:OnIceCandidate 可能在 SetLocalDescription 前就触发(尤其 STUN 延迟低时),此时若信令通道未就绪,候选者就丢了。正确做法是缓存候选者列表,等信令通道 ready 后批量发送。
webrtc::PeerConnectionInterface::IceConnectionState 为 kIceConnectionFailed 时,检查日志里是否有 "Failed to connect to STUN server" 或 "No host candidates gathered"
pc->GetStats 查 RTCIceCandidatePairStats 的 state 字段,确认是否走到 succeeded
RTCConfiguration 中 type 设为 cricket::RELAY_PORT_TYPE,并确保 TURN 凭据有效(username/password 过期会导致 ice_connection_state 卡在 checking)WebRTC C++ 不提供内置播放器,你得自己实现 webrtc::VideoSinkInterface<:videoframe>,并在 OnFrame 里把 webrtc::VideoFrame 的 video_frame_buffer() 转成 OpenGL / Vulkan / Direct3D 可用纹理。缓冲区格式通常是 I420 或 NV12,frame.buffer()->ToI420() 会触发拷贝,性能敏感场景应直接读原始 rtc::scoped_refptr<:videoframebuffer>。
另一个坑是线程:渲染回调默认在 WebRTC 的 worker_thread 上触发,若你的渲染循环在主线程,必须用 PostTask 跨线程投递帧数据,否则 OpenGL 上下文错乱或崩溃。
video_track->AddSink(your_sink),不是 pc->AddSink
your_sink 生命周期长于 video_track,否则 AddSink 后立即析构 sink 会 crashwebrtc::test::FakeVideoRenderer 替代自研渲染器,验证是否为渲染逻辑问题最麻烦的从来不是编译通过,而是 ICE candidate 发送顺序错乱、视频帧时间戳跳变、或某条 AddIceCandidate 调用因线程竞争被丢弃——这些都不会抛异常,只表现为黑屏、单向通话、或连接秒断。建议从最小闭环(STUN + 本地回环)开始,逐项打开功能,用 pc->GetStats 和日志里的 webrtc::Logging 输出交叉验证每一步状态。