必须在信令交换前注册OnTrack和OnICECandidate,否则track被静默丢弃、candidate无法上报,NAT穿透失败;二者须在SetRemoteDescription前设置,且OnICECandidate需点对点发送candidate而非广播。

PeerConnection 必须在信令交换前完成初始化,否则 OnTrack、OnICECandidate 等回调不会生效,连接必然失败。
为什么 newPeerConnection() 后必须立刻注册 OnTrack 和 OnICECandidate
WebRTC 的 Go 实现(如 pion/webrtc/v4)是事件驱动的。一旦远端发送 SDP offer,本地 PeerConnection 就开始收集 ICE 候选、尝试连接;但若没提前设置 OnTrack,收到媒体流时无处转发,track 会被静默丢弃;若没设 OnICECandidate,候选无法上报给对端,NAT 穿透直接卡死。
-
OnTrack是唯一能拿到*webrtc.TrackRemote的入口,漏掉就收不到音视频数据 -
OnICECandidate回调里必须把candidate.String()通过信令通道发给对端,否则对方不知道怎么连你 - 这两个回调必须在
SetRemoteDescription之前注册,否则首次触发可能丢失
信令不是 WebRTC 标准的一部分,但你得自己搭 WebSocket 服务
WebRTC 不规定信令怎么传,但实际开发中几乎都用 gorilla/websocket。浏览器端和 Go 后端靠它交换 offer、answer 和 candidate 字符串。常见错误是只实现单向广播,而 P2P 要求点对点路由——比如用 map[string]*websocket.Conn 按房间 ID 或用户 ID 维护连接句柄。
- WebSocket 连接需禁用
CheckOrigin(开发环境),或按域名白名单校验(生产) - 消息格式建议统一为 JSON,含
type字段("offer"/"answer"/"candidate"),避免字符串拼接解析错位 - 不要在
ReadMessage后直接WriteMessage回显——那是回环测试,不是信令转发
SDP 协商失败的三个高频原因
浏览器打不开画面、日志显示 failed to set remote offer 或 no common codecs,大概率是 SDP 协商环节出问题。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- Go 端默认启用
VP8、Opus,但若浏览器(如 Safari)不支持 VP8,需显式添加H264:在webrtc.Configuration中配置MediaEngine并注册编码器 -
SetRemoteDescription必须在SetLocalDescription之后调用,顺序颠倒会报InvalidStateError - offer/answer 的
type字段必须严格匹配:"offer"只能由 caller 发,"answer"只能由 callee 发,且双方session-id要一致(靠信令层保证)
音频播放要绕过系统音频设备抽象层
Go 本身没有跨平台音频播放 API,github.com/gen2brain/malgo 是目前最稳定的方案,但它要求你手动解码 RTP 包、重采样、喂给音频设备缓冲区。别指望 track.Read() 返回 PCM —— 它返回的是原始 RTP packet,需先剥离 header,再用 g711.Decode 或 opus.NewDecoder 解码。
立即学习“go语言免费学习笔记(深入)”;
- 音频 track 的
Codec().MimeType决定解码方式:"audio/opus"用 Opus 解码,"audio/PCMU"用 G.711 μ-law - malgo 初始化时指定采样率(通常 48kHz)、声道数(1 或 2)、样本位宽(16-bit),与解码输出严格对齐
- 别在主线程里做解码+播放——用 goroutine 处理
track.Read()循环,避免阻塞信令和 ICE 流程
webrtc.ICEConnectionStateConnected 才算通)。这些状态分散在多个 goroutine 里,靠 channel 或 mutex 同步稍有不慎就会 deadlock 或 race。调试时优先看 pc.OnConnectionStateChange 日志,比盲目查 SDP 更快定位断连根源。

















