音视频开发实战
第21章:WebRTC Native 开发
本章目标:掌握 WebRTC Native 开发,使用 C++ 实现完整的实时通信客户端。
在上一章(第20章)中,我们深入理解了 WebRTC 的协议栈,包括 SDP、DTLS、SRTP 和 DataChannel。理论知识为实践奠定了基础,但真正的工程师需要在代码中落地这些概念。
本章将带领你进入 WebRTC Native 开发 的世界。与浏览器中简单的 JavaScript API 不同,Native 开发让你能够: - 深度定制:修改编解码器参数、优化传输策略 - 跨平台部署:从嵌入式设备到服务器端应用 - 性能优化:充分利用硬件加速和系统资源 - 集成现有系统:与 C/C++ 遗留代码无缝对接
学习本章后,你将能够: - 获取和编译 WebRTC 源码 - 使用 PeerConnection API 建立连接 - 管理音视频轨道和设备 - 实现 WebSocket 信令交互 - 构建完整的 1v1 连麦客户端
目录
1. WebRTC Native 简介
1.1 Native 与浏览器版的区别
WebRTC 最初是为浏览器设计的,但其核心库是用 C++ 编写的。Google 将这部分代码开源为 WebRTC Native API,让开发者能够在浏览器之外使用 WebRTC。
| 特性 | 浏览器版 (JavaScript) | Native 版 (C++) |
|---|---|---|
| API 复杂度 | 简单,高度封装 | 复杂,需要手动管理 |
| 灵活性 | 受限(浏览器决定) | 高(完全可控) |
| 性能 | 受浏览器沙箱限制 | 可直接优化系统资源 |
| 部署环境 | 仅限浏览器 | 任何支持 C++ 的平台 |
| 调试能力 | 受限 | 完整调试和性能分析 |
| 包大小 | 浏览器自带 | 需静态链接(~20MB) |
1.2 适用场景
优先选择 Native WebRTC 的场景:
- 嵌入式设备:IP 摄像头、可视门铃、智能眼镜
- 服务器端:SFU 转发服务器、录制服务、AI 处理节点
- 桌面应用:游戏语音、远程桌面、视频会议客户端
- 性能敏感:需要极致优化的实时通信场景
优先选择浏览器版的场景:
- 快速原型:无需编译,即写即测
- Web 应用:无需用户安装,即点即用
- 跨平台:一次开发,多浏览器运行
1.3 Native 架构概览
WebRTC Native 的核心组件:
┌─────────────────────────────────────────────────────────────┐
│ 应用层 (你的代码) │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ PeerConnection│ │ AudioTrack │ │ VideoTrack │ │
│ │ Observer │ │ Source │ │ Source │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ WebRTC API 层 (libwebrtc) │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ PeerConnection│ │ Audio Engine │ │ Video Engine │ │
│ │ Factory │ │ │ │ │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ WebRTC 内部模块 │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
│ │ ICE │ │ DTLS │ │ SRTP │ │ Network │ │
│ │ Agent │ │ Client │ │ Session │ │ Thread │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ 系统层 │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
│ │ Socket │ │ ALSA/ │ │ Camera │ │ GPU │ │
│ │ API │ │ CoreAudio│ │ Capture │ │ Encoder │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
1.4 开发环境准备
系统要求: - Linux: Ubuntu 18.04+ / Debian 9+ / CentOS 7+ - macOS: 10.14+ (Mojave) - Windows: Windows 10 + Visual Studio 2019+
依赖工具: - Python 3.8+(depot_tools 要求) - Git - CMake 3.16+(构建示例项目)
2. 编译 WebRTC
2.1 获取源码
WebRTC 使用 Chromium 的 depot_tools 进行代码管理:
# 1. 安装 depot_tools
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
export PATH="$PWD/depot_tools:$PATH"
# 2. 创建工作目录
mkdir webrtc-checkout
cd webrtc-checkout
# 3. 获取代码(约 10GB,需要稳定网络)
fetch --nohooks webrtc
cd src
# 4. 同步依赖
gclient sync国内镜像加速: 由于网络原因,国内开发者可以使用镜像:
# 使用清华大学镜像
export DEPOT_TOOLS_UPDATE=0
export GYP_GENERATORS=ninja2.2 GN 构建系统
WebRTC 使用 GN(Generate Ninja)作为元构建系统,生成 Ninja 构建文件。
GN 基础概念: - .gn
文件:定义构建根和默认参数 - BUILD.gn 文件:定义构建目标 -
args.gn 文件:存储构建参数
常用构建参数:
| 参数 | 说明 | 常用值 |
|---|---|---|
target_os |
目标操作系统 | "linux", "mac",
"win" |
target_cpu |
目标架构 | "x64", "arm64",
"arm" |
is_debug |
调试构建 | true /
false |
is_component_build |
动态库构建 | true /
false |
rtc_use_h264 |
启用 H.264 | true /
false |
rtc_include_tests |
包含测试 | true /
false |
2.3 编译步骤
# 1. 生成构建配置
gn gen out/Default --args='
target_os="linux"
target_cpu="x64"
is_debug=false
is_component_build=false
rtc_use_h264=true
rtc_include_tests=false
'
# 2. 开始编译(约需 30-60 分钟)
ninja -C out/Default
# 3. 验证编译结果
ls out/Default/libwebrtc.a # 静态库
ls out/Default/peerconnection_client # 示例程序2.4 提取所需模块
完整的 WebRTC 库很大(~20MB),实际项目中可能只需要部分功能。
最小化提取:
# 创建提取脚本
mkdir -p webrtc-minimal/include
mkdir -p webrtc-minimal/lib
# 复制头文件(关键模块)
rsync -av --include='*/' --include='*.h' --exclude='*' \
api/ webrtc-minimal/include/api/
rsync -av --include='*/' --include='*.h' --exclude='*' \
pc/ webrtc-minimal/include/pc/
rsync -av --include='*/' --include='*.h' --exclude='*' \
media/ webrtc-minimal/include/media/
# 复制库文件
cp out/Default/libwebrtc.a webrtc-minimal/lib/3. PeerConnection API
3.1 核心概念
PeerConnection 是 WebRTC Native 的核心类,代表与对端的连接。
主要组件: - PeerConnectionFactory:创建 PeerConnection 的工厂 - PeerConnectionInterface:连接接口 - Observer:异步事件回调
3.2 PeerConnection 状态机
namespace live {
// 信令状态
enum class SignalingState {
STABLE, // 稳定状态
HAVE_LOCAL_OFFER, // 已创建本地 Offer
HAVE_REMOTE_OFFER, // 收到远端 Offer
HAVE_LOCAL_ANSWER, // 已创建本地 Answer
HAVE_REMOTE_ANSWER // 收到远端 Answer
};
// ICE 连接状态
enum class IceConnectionState {
NEW, // 初始状态
CHECKING, // 检测连通性
CONNECTED, // 已连接
COMPLETED, // ICE 完成
FAILED, // 连接失败
DISCONNECTED, // 连接断开
CLOSED // 连接关闭
};
// 连接状态
enum class PeerConnectionState {
NEW,
CONNECTING,
CONNECTED,
DISCONNECTED,
FAILED,
CLOSED
};
} // namespace live3.3 初始化与创建
namespace live {
// PeerConnection 观察者接口
class PeerConnectionObserver {
public:
virtual ~PeerConnectionObserver() = default;
// ICE 候选收集完成回调
virtual void OnIceCandidate(const std::string& sdp_mid,
int sdp_mline_index,
const std::string& candidate) = 0;
// 连接状态变化回调
virtual void OnIceConnectionStateChange(IceConnectionState state) = 0;
// 数据通道创建回调
virtual void OnDataChannel(std::shared_ptr<DataChannel> channel) = 0;
// 远端轨道添加回调
virtual void OnTrack(std::shared_ptr<RtpTransceiver> transceiver) = 0;
// 重协商需要回调
virtual void OnRenegotiationNeeded() = 0;
};
// PeerConnection 封装类
class PeerConnectionClient {
public:
PeerConnectionClient();
~PeerConnectionClient();
// 初始化(必须在任何其他操作之前调用)
bool Initialize();
// 创建 PeerConnection
bool CreatePeerConnection(const IceServers& ice_servers,
PeerConnectionObserver* observer);
// 创建 Offer
void CreateOffer(std::function<void(const std::string& sdp,
const std::string& type)> callback);
// 创建 Answer
void CreateAnswer(std::function<void(const std::string& sdp,
const std::string& type)> callback);
// 设置本地描述
void SetLocalDescription(const std::string& sdp,
const std::string& type,
std::function<void(bool)> callback);
// 设置远端描述
void SetRemoteDescription(const std::string& sdp,
const std::string& type,
std::function<void(bool)> callback);
// 添加 ICE 候选
void AddIceCandidate(const std::string& sdp_mid,
int sdp_mline_index,
const std::string& candidate);
// 添加媒体轨道
void AddTrack(std::shared_ptr<MediaStreamTrack> track,
const std::vector<std::string>& stream_ids);
// 关闭连接
void Close();
private:
class Impl;
std::unique_ptr<Impl> impl_;
};
} // namespace live3.4 ICE 服务器配置
namespace live {
// ICE 服务器配置
struct IceServer {
std::string uri; // STUN/TURN 服务器地址
std::string username; // TURN 用户名
std::string password; // TURN 密码
};
using IceServers = std::vector<IceServer>;
// 创建默认 ICE 配置
inline IceServers CreateDefaultIceServers() {
return {
// Google 公共 STUN 服务器
{"stun:stun.l.google.com:19302", "", ""},
{"stun:stun1.l.google.com:19302", "", ""},
// 自定义 TURN 服务器(生产环境必须部署)
// {"turn:your-turn-server.com:3478", "user", "pass"}
};
}
} // namespace live3.5 CreateOffer/Answer 流程
namespace live {
// SDP 描述封装
struct SessionDescription {
std::string type; // "offer" 或 "answer"
std::string sdp; // SDP 文本
bool IsValid() const {
return !type.empty() && !sdp.empty();
}
};
// Offer/Answer 协商流程
class SdpNegotiator {
public:
// 创建 Offer(发起方调用)
void CreateOffer(PeerConnectionClient* pc,
std::function<void(const SessionDescription&)> callback) {
pc->CreateOffer([callback](const std::string& sdp,
const std::string& type) {
callback(SessionDescription{type, sdp});
});
}
// 创建 Answer(接收方调用)
void CreateAnswer(PeerConnectionClient* pc,
const SessionDescription& remote_offer,
std::function<void(const SessionDescription&)> callback) {
// 1. 先设置远端 Offer
pc->SetRemoteDescription(remote_offer.sdp, remote_offer.type,
[pc, callback](bool success) {
if (!success) {
callback(SessionDescription{});
return;
}
// 2. 创建 Answer
pc->CreateAnswer([callback](const std::string& sdp,
const std::string& type) {
callback(SessionDescription{type, sdp});
});
});
}
// 完成协商(双方都调用)
void CompleteNegotiation(PeerConnectionClient* pc,
const SessionDescription& remote_desc,
std::function<void(bool)> callback) {
pc->SetRemoteDescription(remote_desc.sdp, remote_desc.type, callback);
}
};
} // namespace live4. 音视频轨道管理
4.1 MediaStream 与 Track
核心概念: - MediaStream:媒体流的容器,包含多个 Track - AudioTrack:音频轨道 - VideoTrack:视频轨道 - TrackSource:轨道数据源(采集设备或自定义数据)
4.2 音频轨道管理
namespace live {
// 音频设备管理
class AudioDeviceManager {
public:
// 获取可用设备列表
std::vector<AudioDevice> GetRecordingDevices();
std::vector<AudioDevice> GetPlayoutDevices();
// 选择设备
bool SetRecordingDevice(int index);
bool SetPlayoutDevice(int index);
// 创建音频轨道
std::shared_ptr<AudioTrack> CreateAudioTrack(
const std::string& track_id,
std::shared_ptr<PeerConnectionFactory> factory);
};
// 音频轨道配置
struct AudioTrackConfig {
int sample_rate_hz = 48000; // 采样率
int channels = 2; // 声道数
bool echo_cancellation = true; // 回声消除
bool noise_suppression = true; // 噪声抑制
bool auto_gain_control = true; // 自动增益
};
// 音频轨道封装
class AudioTrackWrapper {
public:
AudioTrackWrapper(const std::string& track_id,
std::shared_ptr<PeerConnectionFactory> factory);
// 初始化
bool Initialize(const AudioTrackConfig& config);
// 获取底层轨道
std::shared_ptr<AudioTrack> GetTrack() const { return track_; }
// 静音控制
void SetMuted(bool muted);
bool IsMuted() const;
// 音量控制 (0.0 - 1.0)
void SetVolume(double volume);
double GetVolume() const;
private:
std::string track_id_;
std::shared_ptr<AudioTrack> track_;
std::shared_ptr<AudioSource> source_;
};
} // namespace live4.3 视频轨道管理
namespace live {
// 视频捕获设备
struct VideoCaptureDevice {
std::string device_id;
std::string friendly_name;
std::vector<VideoFormat> supported_formats;
};
// 视频格式
struct VideoFormat {
int width;
int height;
int fps;
VideoCodecType codec; // I420, NV12, etc.
};
// 视频轨道配置
struct VideoTrackConfig {
int width = 1280;
int height = 720;
int fps = 30;
VideoCodecType capture_format = VideoCodecType::kI420;
};
// 视频轨道封装
class VideoTrackWrapper {
public:
VideoTrackWrapper(const std::string& track_id,
std::shared_ptr<PeerConnectionFactory> factory);
// 初始化摄像头
bool InitializeCamera(const std::string& device_id,
const VideoTrackConfig& config);
// 初始化屏幕捕获
bool InitializeScreenCapture(int screen_index,
const VideoTrackConfig& config);
// 使用自定义视频源
bool InitializeCustomSource(std::shared_ptr<VideoSource> source);
// 获取底层轨道
std::shared_ptr<VideoTrack> GetTrack() const { return track_; }
// 启用/禁用
void SetEnabled(bool enabled);
// 添加渲染器
void AddRenderer(std::shared_ptr<VideoRenderer> renderer);
void RemoveRenderer(std::shared_ptr<VideoRenderer> renderer);
private:
std::string track_id_;
std::shared_ptr<VideoTrack> track_;
std::shared_ptr<VideoSource> source_;
std::shared_ptr<VideoCapturer> capturer_;
};
} // namespace live4.4 自定义视频源
namespace live {
// 自定义视频源(用于推送编码后的数据)
class CustomVideoSource : public VideoSource {
public:
CustomVideoSource(int width, int height, int fps);
// 推送帧数据
void PushFrame(const uint8_t* data, size_t len,
int64_t timestamp_us);
// 推送 I420 格式的帧
void PushI420Frame(const uint8_t* y_plane, int y_stride,
const uint8_t* u_plane, int u_stride,
const uint8_t* v_plane, int v_stride,
int width, int height,
int64_t timestamp_us);
// 推送 NV12 格式的帧
void PushNV12Frame(const uint8_t* y_plane, int y_stride,
const uint8_t* uv_plane, int uv_stride,
int width, int height,
int64_t timestamp_us);
private:
int width_;
int height_;
int fps_;
std::atomic<int64_t> last_timestamp_{0};
};
} // namespace live4.5 远端轨道接收
namespace live {
// 远端音频轨道处理
class RemoteAudioTrackHandler {
public:
void OnRemoteAudioTrack(std::shared_ptr<AudioTrack> track) {
track_ = track;
// 添加音频接收回调
track->AddSink(this);
}
// 音频数据回调
void OnData(const void* audio_data,
int bits_per_sample,
int sample_rate,
size_t number_of_channels,
size_t number_of_frames) {
// 处理接收到的音频数据
// 例如:播放、录制、分析
ProcessAudio(audio_data, bits_per_sample, sample_rate,
number_of_channels, number_of_frames);
}
private:
std::shared_ptr<AudioTrack> track_;
void ProcessAudio(const void* data, int bits_per_sample,
int sample_rate, size_t channels, size_t frames);
};
// 远端视频轨道处理
class RemoteVideoTrackHandler {
public:
void OnRemoteVideoTrack(std::shared_ptr<VideoTrack> track) {
track_ = track;
// 添加视频渲染器
renderer_ = std::make_shared<CustomVideoRenderer>();
track->AddOrUpdateSink(renderer_.get(), rtc::VideoSinkWants());
}
private:
std::shared_ptr<VideoTrack> track_;
std::shared_ptr<CustomVideoRenderer> renderer_;
};
} // namespace live5. 信令集成
5.1 信令流程概览
信令的核心作用: 1. 交换 SDP(Offer/Answer) 2. 交换 ICE 候选 3. 传递控制消息(静音、挂断等)
5.2 WebSocket 信令客户端
namespace live {
// WebSocket 信令客户端
class WebSocketSignalingClient : public PeerConnectionObserver {
public:
WebSocketSignalingClient();
~WebSocketSignalingClient();
// 连接到信令服务器
bool Connect(const std::string& url);
void Disconnect();
// 加入房间
void JoinRoom(const std::string& room_id, const std::string& user_id);
// 离开房间
void LeaveRoom();
// 发送 SDP
void SendSdp(const std::string& type, const std::string& sdp);
// 发送 ICE 候选
void SendIceCandidate(const std::string& sdp_mid,
int sdp_mline_index,
const std::string& candidate);
// 设置事件回调
using SdpCallback = std::function<void(const std::string& type,
const std::string& sdp)>;
using IceCallback = std::function<void(const std::string& sdp_mid,
int sdp_mline_index,
const std::string& candidate)>;
using PeerCallback = std::function<void(const std::string& peer_id,
bool joined)>;
void SetSdpCallback(SdpCallback cb) { sdp_callback_ = cb; }
void SetIceCallback(IceCallback cb) { ice_callback_ = cb; }
void SetPeerCallback(PeerCallback cb) { peer_callback_ = cb; }
private:
// WebSocket 回调
void OnWebSocketMessage(const std::string& message);
void OnWebSocketConnected();
void OnWebSocketDisconnected();
// 信令消息处理
void HandleSignalingMessage(const json& msg);
void HandleSdpMessage(const json& msg);
void HandleIceMessage(const json& msg);
void HandlePeerEvent(const json& msg);
std::unique_ptr<WebSocketClient> ws_client_;
std::string room_id_;
std::string user_id_;
SdpCallback sdp_callback_;
IceCallback ice_callback_;
PeerCallback peer_callback_;
};
} // namespace live5.3 信令消息格式
namespace live {
// 信令消息类型
enum class SignalingMessageType {
JOIN, // 加入房间
LEAVE, // 离开房间
OFFER, // SDP Offer
ANSWER, // SDP Answer
ICE_CANDIDATE, // ICE 候选
PEER_JOINED, // 对端加入
PEER_LEFT, // 对端离开
ERROR // 错误
};
// 消息结构定义
/*
// 加入房间
{
"type": "join",
"room_id": "room-123",
"user_id": "user-abc"
}
// SDP 交换
{
"type": "offer", // 或 "answer"
"sdp": "v=0\r\no=- 123...",
"from": "user-abc",
"to": "user-xyz"
}
// ICE 候选
{
"type": "ice_candidate",
"sdp_mid": "audio",
"sdp_mline_index": 0,
"candidate": "candidate:1 1 UDP 2122252543 192.168.1.10 5000 typ host",
"from": "user-abc",
"to": "user-xyz"
}
// 对端事件
{
"type": "peer_joined",
"peer_id": "user-xyz"
}
*/
// 消息序列化/反序列化
class SignalingMessageParser {
public:
static std::string SerializeJoin(const std::string& room_id,
const std::string& user_id);
static std::string SerializeSdp(const std::string& type,
const std::string& sdp,
const std::string& to);
static std::string SerializeIceCandidate(const std::string& sdp_mid,
int sdp_mline_index,
const std::string& candidate,
const std::string& to);
static bool ParseMessage(const std::string& json_str,
SignalingMessageType* type,
json* payload);
};
} // namespace live5.4 SDP 交换流程实现
namespace live {
// 完整的信令协调器
class SignalingCoordinator : public PeerConnectionObserver {
public:
SignalingCoordinator(std::shared_ptr<WebSocketSignalingClient> signaling,
std::shared_ptr<PeerConnectionClient> pc)
: signaling_(signaling), pc_(pc) {
// 注册信令回调
signaling_->SetSdpCallback(
[this](const std::string& type, const std::string& sdp) {
OnRemoteSdp(type, sdp);
});
signaling_->SetIceCallback(
[this](const std::string& sdp_mid, int index,
const std::string& candidate) {
pc_->AddIceCandidate(sdp_mid, index, candidate);
});
}
// 发起通话
void StartCall() {
// 1. 添加本地轨道
AddLocalTracks();
// 2. 创建 Offer
pc_->CreateOffer([this](const std::string& sdp,
const std::string& type) {
// 3. 设置本地描述
pc_->SetLocalDescription(sdp, type, [this, sdp](bool success) {
if (success) {
// 4. 发送 Offer
signaling_->SendSdp("offer", sdp);
}
});
});
}
// 收到远端 SDP
void OnRemoteSdp(const std::string& type, const std::string& sdp) {
if (type == "offer") {
// 作为 Answer 方
HandleRemoteOffer(sdp);
} else if (type == "answer") {
// 作为 Offer 方,收到 Answer
pc_->SetRemoteDescription(sdp, "answer",
[](bool success) {
LOG_INFO("Answer set: {}", success);
});
}
}
// PeerConnectionObserver 回调
void OnIceCandidate(const std::string& sdp_mid,
int sdp_mline_index,
const std::string& candidate) override {
// 发送 ICE 候选到对端
signaling_->SendIceCandidate(sdp_mid, sdp_mline_index, candidate);
}
void OnIceConnectionStateChange(IceConnectionState state) override {
LOG_INFO("ICE state: {}", static_cast<int>(state));
switch (state) {
case IceConnectionState::CONNECTED:
case IceConnectionState::COMPLETED:
LOG_INFO("Connected!");
break;
case IceConnectionState::FAILED:
LOG_ERROR("Connection failed");
break;
default:
break;
}
}
private:
void AddLocalTracks() {
// 添加音频轨道
auto audio_track = audio_device_->CreateAudioTrack("audio", factory_);
pc_->AddTrack(audio_track, {"stream-1"});
// 添加视频轨道
auto video_track = video_device_->CreateVideoTrack("video", factory_);
pc_->AddTrack(video_track, {"stream-1"});
}
void HandleRemoteOffer(const std::string& sdp) {
// 1. 设置远端 Offer
pc_->SetRemoteDescription(sdp, "offer", [this](bool success) {
if (!success) {
LOG_ERROR("Failed to set remote offer");
return;
}
// 2. 添加本地轨道
AddLocalTracks();
// 3. 创建 Answer
pc_->CreateAnswer([this](const std::string& sdp,
const std::string& type) {
// 4. 设置本地 Answer
pc_->SetLocalDescription(sdp, type, [this, sdp](bool success) {
if (success) {
// 5. 发送 Answer
signaling_->SendSdp("answer", sdp);
}
});
});
});
}
std::shared_ptr<WebSocketSignalingClient> signaling_;
std::shared_ptr<PeerConnectionClient> pc_;
std::shared_ptr<AudioDeviceManager> audio_device_;
std::shared_ptr<VideoDeviceManager> video_device_;
std::shared_ptr<PeerConnectionFactory> factory_;
};
} // namespace live6. 完整连麦客户端实现
6.1 项目结构
webrtc-call-client/
├── CMakeLists.txt
├── src/
│ ├── main.cpp
│ ├── peer_connection_client.{h,cpp}
│ ├── websocket_signaling.{h,cpp}
│ ├── audio_device.{h,cpp}
│ ├── video_device.{h,cpp}
│ └── signaling_coordinator.{h,cpp}
└── include/
└── live/
└── webrtc/
6.2 CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(webrtc_call_client VERSION 1.0.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 查找 WebRTC
find_package(WebRTC REQUIRED)
# 查找其他依赖
find_package(Threads REQUIRED)
find_package(OpenSSL REQUIRED)
# 可执行文件
add_executable(webrtc_call_client
src/main.cpp
src/peer_connection_client.cpp
src/websocket_signaling.cpp
src/audio_device.cpp
src/video_device.cpp
src/signaling_coordinator.cpp
)
target_include_directories(webrtc_call_client PRIVATE
${CMAKE_SOURCE_DIR}/include
${WEBRTC_INCLUDE_DIRS}
)
target_link_libraries(webrtc_call_client PRIVATE
${WEBRTC_LIBRARIES}
Threads::Threads
OpenSSL::SSL
OpenSSL::Crypto
)
# 编译选项
target_compile_options(webrtc_call_client PRIVATE
-Wall
-Wextra
-O2
)6.3 主程序
// src/main.cpp
#include <iostream>
#include <string>
#include <memory>
#include <signal.h>
#include "live/webrtc/peer_connection_client.h"
#include "live/webrtc/websocket_signaling.h"
#include "live/webrtc/signaling_coordinator.h"
using namespace live;
static std::atomic<bool> g_running{true};
void SignalHandler(int sig) {
g_running = false;
}
void PrintUsage(const char* program) {
std::cout << "Usage: " << program << " [options]\n"
<< "Options:\n"
<< " --server <url> Signaling server URL (ws://host:port)\n"
<< " --room <id> Room ID to join\n"
<< " --user <id> User ID\n"
<< " --help Show this help\n";
}
int main(int argc, char* argv[]) {
// 解析命令行参数
std::string server_url = "ws://localhost:8080";
std::string room_id = "test-room";
std::string user_id = "user-" + std::to_string(getpid());
for (int i = 1; i < argc; ++i) {
std::string arg = argv[i];
if (arg == "--server" && i + 1 < argc) {
server_url = argv[++i];
} else if (arg == "--room" && i + 1 < argc) {
room_id = argv[++i];
} else if (arg == "--user" && i + 1 < argc) {
user_id = argv[++i];
} else if (arg == "--help") {
PrintUsage(argv[0]);
return 0;
}
}
// 设置信号处理
signal(SIGINT, SignalHandler);
signal(SIGTERM, SignalHandler);
std::cout << "WebRTC Call Client\n"
<< "Server: " << server_url << "\n"
<< "Room: " << room_id << "\n"
<< "User: " << user_id << "\n\n";
// 初始化 PeerConnection
auto pc_client = std::make_shared<PeerConnectionClient>();
if (!pc_client->Initialize()) {
std::cerr << "Failed to initialize PeerConnection\n";
return 1;
}
// 配置 ICE 服务器
auto ice_servers = CreateDefaultIceServers();
if (!pc_client->CreatePeerConnection(ice_servers, nullptr)) {
std::cerr << "Failed to create PeerConnection\n";
return 1;
}
// 连接信令服务器
auto signaling = std::make_shared<WebSocketSignalingClient>();
if (!signaling->Connect(server_url)) {
std::cerr << "Failed to connect to signaling server\n";
return 1;
}
// 创建信令协调器
auto coordinator = std::make_shared<SignalingCoordinator>(
signaling, pc_client);
// 加入房间
signaling->JoinRoom(room_id, user_id);
// 主循环
std::cout << "Press Ctrl+C to exit\n";
while (g_running) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
// 清理
signaling->LeaveRoom();
signaling->Disconnect();
pc_client->Close();
std::cout << "\nBye!\n";
return 0;
}7. 本章总结
7.1 核心知识点回顾
| 概念 | 作用 | 关键 API |
|---|---|---|
| PeerConnectionFactory | 创建所有 WebRTC 对象 | CreatePeerConnectionFactory() |
| PeerConnection | 管理连接生命周期 | CreateOffer(),
SetLocalDescription() |
| MediaStreamTrack | 音视频数据传输 | AddTrack(),
OnTrack() |
| SDP | 媒体协商 | CreateOffer(),
CreateAnswer() |
| ICE | 连接建立 | OnIceCandidate(),
AddIceCandidate() |
| 信令 | 协调连接建立 | WebSocket 交换 SDP/ICE |
7.2 WebRTC Native 开发流程
1. 获取源码 → 2. 编译 → 3. 初始化 Factory → 4. 创建 PC → 5. 添加轨道
↓
6. 信令连接 → 7. 交换 SDP → 8. ICE 协商 → 9. DTLS 握手 → 10. 媒体传输
7.3 常见问题与解决
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 编译失败 | 依赖缺失 | 检查 depot_tools,使用
gclient sync |
| 连接失败 | ICE 不通 | 部署 TURN 服务器,检查防火墙 |
| 无视频 | 权限/设备 | 检查摄像头权限,确认设备 ID 正确 |
| 回声 | 3A 未启用 | 确保 AEC/ANS/AGC 开启 |
| 延迟高 | 编码参数 | 降低分辨率/帧率,调整 buffer 大小 |
7.4 与下一章的衔接
本章我们掌握了 WebRTC Native 开发,实现了 1v1 连麦客户端。但在实际生产环境中,1v1 只是基础场景。
下一章(第22章)SFU 转发服务器 将学习: - P2P 的局限性:为什么需要服务器中转 - SFU(Selective Forwarding Unit)架构设计 - Simulcast 多路质量转发 - GCC 拥塞控制和带宽估计 - 实现一个简单的 SFU 服务器
SFU 是多人视频会议的核心技术,它将让我们从 1v1 走向 1vN 的实时通信世界。
课后实践
修改示例代码,实现一个简单的文件传输功能(使用 DataChannel)
添加统计功能,定期打印连接状态、码率、丢包率等信息
实现屏幕共享,将摄像头替换为屏幕捕获源
部署信令服务器,使用 Node.js + WebSocket 实现一个支持多房间的简单信令服务