1. 从 peerconnection_client 的 SDP 协商说起

WebRTC 一对一通话里,peerconnection_client 是很多人接触 Native 端 WebRTC 的第一个示例工程。它能做什么?简单说,它把「信令连接、SDP offer/answer 交换、ICE candidate 收集、音视频轨道渲染」这条完整链路串了起来,适合想搞懂 PeerConnection 生命周期、又不想一上来就啃底层编解码的开发者。适合谁?适合已经能编译出 all.sln、手里有 peerconnection_client.exe,但一跑起来就卡在「登录成功却看不到对方画面」或者「SDP 交换后 ICE 一直 checking」的人。

我这次的目标不是重讲一遍 WebRTC 原理,而是把 peerconnection_client 的信令与 SDP 交换流程拆开,同时给出一套可复制的 settings.json 配置骨架,把 AI 辅助调试工具通过 TaoToken 统一 Key 接进来。这样你在本地复现通话链路时,遇到 SDP 协商问题能有一份结构化的日志和配置对照,而不是靠猜。

需要先明确一点:peerconnection_client 自带的信令是 HTTP 轮询式的,peer_connection_client.cc 里用 PeerConnectionClient 负责和信令服务器交互,conductor.cc 负责 WebRTC 核心逻辑。SDP 的生成、保存、发送,全部发生在 Conductor 继承的几个 Observer 回调里。理解这几个回调的触发顺序,比死记 API 名字有用得多。

2. TaoToken 统一 Key 在调试链路里的位置

在讲配置骨架之前,先说清楚 TaoToken 在这里扮演什么角色。它不是信令服务器,也不参与 SDP 交换本身,而是给 AI 辅助调试工具提供统一的模型调用通道。你可以把它理解成一个「统一 Key 网关」:本地调试脚本、日志分析工具、甚至你在 IDE 里挂的辅助插件,都通过同一个 Key 和同一套 API 地址去请求模型,不用每个工具单独配一套凭证。

官网入口在 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 基址是 https://taotoken.net/api 。注意 API 地址不带 UTM 参数,配置里填干净的这个就行。

为什么调试 WebRTC 需要它?因为 SDP 文本又长又绕,m= 行、a=candidate 行、a=rtpmap 行混在一起,人工比对两端 SDP 差异很费眼。把两端 SDP 丢给模型做结构化对比,能快速定位是 codec 不匹配、还是 candidate 没交换成功。TaoToken 的统一 Key 让这件事在多个调试脚本之间保持一致,不用来回换凭证。

如果你只是验证模型对话能力,可以直接用模型对话页面;如果是长期做编码和 Agent 类调试,建议走 Coding Plan;接入细节看接入文档,Key 的创建和管理在 API Keys 页面。这几个入口后面 CTA 会再给一次。

3. settings.json 配置骨架与 peerconnection_client 参数对照

下面这份 settings.json 是我实际调试时用的骨架,分成三块:信令与 ICE 配置、TaoToken 统一 Key 配置、日志与调试开关。你可以直接复制,把占位符替换成自己的值。

{
  "webrtc": {
    "signaling_server": "http://127.0.0.1",
    "signaling_port": 8888,
    "peer_id": -1,
    "ice_servers": [
      {
        "uri": "stun:stun.l.google.com:19302"
      }
    ],
    "sdp_semantics": "unified_plan",
    "enable_dtls_srtp": true,
    "loopback": false
  },
  "taotoken": {
    "api_base": "https://taotoken.net/api",
    "api_key": "sk-你的统一Key",
    "model": "你的模型名",
    "timeout_ms": 30000
  },
  "debug": {
    "log_sdp": true,
    "log_candidate": true,
    "log_dir": "./webrtc_debug_logs",
    "dump_local_sdp": true,
    "dump_remote_sdp": true
  }
}

这份配置和 peerconnection_client 的命令行参数是对应的。signaling_server 和 signaling_port 对应 --server 和 --port,peer_id 对应 --peer_id,loopback 对应 --loopback。ice_servers 里的 uri 就是 conductor.cc 里 GetPeerConnectionString() 返回的地址,默认是谷歌的 STUN,你可以换成自己的。

taotoken 这一段是给调试脚本读的,不参与 WebRTC 运行时。api_base 固定填 https://taotoken.net/api,api_key 填你在 API Keys 页面创建的统一 Key。model 按你实际可用的模型名填。

debug 这一段最关键。log_sdp 打开后,Conductor::OnSuccess 里生成的本地 SDP 会被落盘;log_candidate 打开后,OnIceCandidate 里收集到的每个 candidate 也会记录。dump_local_sdp 和 dump_remote_sdp 分别对应本地 offer/answer 和远端 answer/offer 的保存,方便你事后做两端对比。

配置加载的代码骨架可以这样写,放在 main.cc 初始化 Conductor 之前:

#include <fstream>
#include <nlohmann/json.hpp>

using json = nlohmann::json;

struct DebugConfig {
  bool log_sdp = false;
  bool log_candidate = false;
  std::string log_dir = "./webrtc_debug_logs";
};

DebugConfig LoadDebugConfig(const std::string& path) {
  DebugConfig cfg;
  std::ifstream ifs(path);
  if (!ifs.is_open()) {
    return cfg;
  }
  json j;
  ifs >> j;
  if (j.contains("debug")) {
    auto& d = j["debug"];
    cfg.log_sdp = d.value("log_sdp", false);
    cfg.log_candidate = d.value("log_candidate", false);
    cfg.log_dir = d.value("log_dir", std::string("./webrtc_debug_logs"));
  }
  return cfg;
}

这段代码只做一件事:把 settings.json 里的 debug 段读进来。nlohmann/json 是单头文件库,直接丢进工程就能用。读完之后,在 Conductor::OnSuccess 和 OnIceCandidate 里根据 log_sdp、log_candidate 决定是否写文件。

4. 验证请求与 SDP 交换成功结果

配置就绪后,启动信令服务器和两个 peerconnection_client 实例,一个作为 caller,一个作为 callee。caller 连上后点击对端 ID 发起连接,此时 Conductor::ConnectToPeer 被调用,InitializePeerConnection 创建 PeerConnectionFactory 和 PeerConnection,然后 CreateOffer 触发。

CreateOffer 的结果回到 Conductor::OnSuccess,这里会做三件事:SetLocalDescription 保存本地 SDP、desc->ToString(&sdp) 序列化、用 JSON 包装后 SendMessage 发给对端。如果你在 settings.json 里开了 log_sdp,这一步会看到类似这样的日志:

[INFO] OnSuccess
[INFO] Local SDP saved, length=2841
[INFO] SendMessage: {"type":"offer","sdp":"v=0\r\no=- 123456 2 IN IP4 127.0.0.1\r\n..."}

对端收到 offer 后,OnMessageFromPeer 解析 JSON,识别出 type 是 offer,调用 SetRemoteDescription 保存远端 SDP,然后 CreateAnswer 生成自己的 answer,同样走 OnSuccess 发回。caller 收到 answer 后再次 SetRemoteDescription,媒体协商阶段结束。

媒体协商结束后,ICE 收集开始。OnIceCandidate 会被反复调用,每个 candidate 都通过 SendMessage 发给对端。对端在 OnMessageFromPeer 里识别出 candidate 消息,调用 AddIceCandidate 保存。当双方 candidate 交换完成,OnIceConnectionChange 会从 kChecking 走到 kConnected,再到 kCompleted。

验证成功的标志有三个:一是两端日志里都出现 OnIceConnectionChange: kConnected;二是 OnAddTrack 被触发,receiver->id() 有值;三是本地窗口开始渲染远端视频帧。如果只走到 kChecking 就停住,说明 candidate 交换有问题,这时候把两端 SDP 和 candidate 日志拿出来对比。

用 TaoToken 做辅助对比时,可以把两端 SDP 拼成一段 prompt,让模型找出差异行。请求示例:

curl -s https://taotoken.net/api/v1/chat/completions \
  -H "Authorization: Bearer sk-你的统一Key" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "你的模型名",
    "messages": [
      {"role": "user", "content": "对比以下两端 SDP,找出 codec 和 candidate 差异:\n本地:...\n远端:..."}
    ]
  }'

返回结果里模型会指出哪一行的 m=video 端口为 0、哪一端的 a=rtpmap 缺少 H264。这比人工逐行扫快很多。

5. 本篇常见错排查

5.1 SDP 交换后 ICE 一直 checking

最常见的原因是 candidate 没发出去或者没收到。先看 OnIceCandidate 有没有被调用,如果没调用,检查 CreatePeerConnection 里 config.servers 是否为空。如果调用了但对端没收到,检查 SendMessage 的 JSON 字段名是否和 OnMessageFromPeer 的解析字段一致。peerconnection_client 默认用 kCandidateSdpMidName、kCandidateSdpMlineIndexName、kCandidateSdpName 三个常量,改过字段名就会对不上。

5.2 OnSuccess 里 SetLocalDescription 失败

SetLocalDescription 失败通常是 SDP 格式问题。OnSuccess 拿到的 desc 是 SessionDescriptionInterface*,直接 ToString 一般没问题,但如果你在中间做了字符串替换,可能破坏 \r\n 行尾。WebRTC 要求 SDP 行尾是 CRLF,不是 LF。检查你的日志落盘时有没有把 \r 吃掉。

5.3 两端 codec 不匹配导致无画面

如果 ICE 到了 kConnected 但 OnAddTrack 没触发,多半是 codec 协商失败。看两端 SDP 的 m=video 段,a=rtpmap 列表必须至少有一个交集。peerconnection_client 默认用内置编解码工厂,如果编译时没开 H264,而对方只发 H264,就会协商失败。这时候要么重新编译带上 H264,要么在 CreatePeerConnectionFactory 时注入外部编解码工厂。

5.4 settings.json 读取失败导致调试日志为空

LoadDebugConfig 里如果 ifs.is_open() 返回 false,会静默返回默认配置,log_sdp 和 log_candidate 都是 false,日志自然为空。检查 settings.json 路径是不是相对于工作目录,而不是相对于可执行文件。建议在 main.cc 里打印一下实际读取的路径,确认文件被找到。

5.5 TaoToken 请求返回 401

统一 Key 没填对,或者 Authorization 头格式不对。正确格式是 Bearer sk-xxx,注意 Bearer 和 Key 之间有一个空格。另外确认 api_base 是 https://taotoken.net/api,不要带末尾斜杠,也不要把 UTM 参数拼进去。

6. 把调试链路固定下来

peerconnection_client 的价值在于它把 WebRTC 的信令流程压缩到了一个可读的工程里。SDP 交换的核心就是 OnSuccess 发本地描述、OnMessageFromPeer 收远端描述、OnIceCandidate 发候选、AddIceCandidate 收候选这四个动作。把这四个动作的日志和 settings.json 里的开关绑在一起,每次调试都有据可查。

TaoToken 统一 Key 在这里的作用是让 SDP 对比、日志分析这类重复劳动交给模型,你专注在信令流程本身。需要创建或管理 Key 就去 API Keys 页面;想先验证模型对话是否通,用模型对话页面;长期做编码和 Agent 调试,看 Coding Plan;接入参数和字段说明,查接入文档。把这几步跑通,下次再遇到 ICE 卡住或者 codec 不匹配,你手里就有完整的 SDP 和 candidate 日志,而不是只有一个「连不上」的现象。

Logo

邀请您加入社区

更多推荐