- WebRTC
- 音视频
- 即时通讯
- 通信
【免费下载链接】webrtc
Pure Go implementation of the WebRTC API
WHIP(WebRTC-HTTP Ingestion Protocol)与 WHEP(WebRTC-HTTP Egress Protocol)是建立在 HTTP 之上的标准化 WebRTC 信令协议,它们将"Offer 通过 HTTP 上传、服务器返回 Answer"这一强 API 契约固定下来,从而让 OBS、GStreamer 等专业推流工具可以直接对接 WebRTC 媒体服务器。本文以 Pion WebRTC 仓库中的 whip-whep 示例 为主体,完整讲解该示例的部署运行、浏览器/OBS 双端推拉流操作,并逐段剖析 main.go 的服务端实现与 index.html 的浏览器端实现,帮助读者掌握用纯 Go 构建一套"推流—中转—订阅"WebRTC 直播链路的完整技术方案。
WHIP/WHEP 解决什么问题
传统 WebRTC 应用的信令(SDP 协商)通常由双方自行约定,而 WHIP/WHEP 把信令固化为两个 HTTP 端点:
- WHIP(推流/上行):发布端(OBS、GStreamer 或浏览器)通过
POST把 SDP Offer 上传到/whip,服务器返回携带完整 ICE Candidate 的 SDP Answer; - WHEP(拉流/下行):订阅端通过
POST把 SDP Offer 上传到/whep,服务器在 Answer 中携带媒体轨道信息,订阅端即可消费同一份媒体流。
正是这种强契约让 OBS、GStreamer 这类原本不原生支持 WebRTC 的工具,无需了解信令细节即可完成 WebRTC 推流。在 whip-whep 示例 中,WHIP 会话接收的 RTP 包会被转发到 WHEP 会话,实现"一端推流、多端亚秒级观看",甚至可以把 WHEP 流再拉回 OBS 做二次处理。
快速运行示例
该示例因为需要对外提供静态 HTML 页面(index.html),所以必须克隆仓库后从示例目录运行:
git clone https://github.com/pion/webrtc.git cd webrtc/examples/whip-whep go run *.go启动成功后终端会打印Open http://localhost:8080 to access this demo,监听端口为8080(见 main.go 中的http.ListenAndServe(":8080", nil))。
通过浏览器发布
打开 http://localhost:8080,点击页面上的Publish按钮。浏览器会调用getUserMedia采集摄像头与麦克风,生成 Offer 后以application/sdp的 Content-TypePOST到/whip,拿到 Answer 即完成协商(见 index.html 的doWHIP函数)。
通过 OBS 发布
在 OBS 中配置推流:
| 配置项 | 取值 |
|---|---|
| Service(服务) | WHIP |
| Server(服务器) | http://localhost:8080/whip |
| Bearer Token | 任意值(示例未校验) |
OBS 会把编码后的音视频以 WHIP 协议推送到该端点。由于 WHIP/WHEP 是标准化信令,GStreamer 等同样支持 WHIP 的推流工具也可按相同方式接入。
订阅观看
保持推流进行中,再次打开 http://localhost:8080 并点击Subscribe。浏览器会添加recvonly音视频 Transceiver,将 OfferPOST到/whep,拿到 Answer 后即可在页面视频区看到刚才推送的画面(见 index.html 的doWHEP函数)。同样,OBS、GStreamer 也可以通过 WHEP 把这条流拉回去。
服务端源码剖析
媒体轨道与 ICE 配置
服务端在main()中先创建两条本地轨道,作为 WHIP→WHEP 的媒体中转载体(main.go):
videoTrack, err = webrtc.NewTrackLocalStaticRTP(webrtc.RTPCodecCapability{ MimeType: webrtc.MimeTypeH264, }, "video", "pion") audioTrack, err = webrtc.NewTrackLocalStaticRTP(webrtc.RTPCodecCapability{ MimeType: webrtc.MimeTypeOpus, }, "audio", "pion")NewTrackLocalStaticRTP 返回一个"已预置编解码、直接接收 RTP 包"的本地轨道:视频为 H264、音频为 Opus,轨道 ID 与流 ID 均为pion。还声明了全局的 PeerConnection 配置,使用 Google 公共 STUN 服务器辅助 NAT 穿透(main.go):
peerConnectionConfiguration = webrtc.Configuration{ ICEServers: []webrtc.ICEServer{ {URLs: []string{"stun:stun.l.google.com:19302"}}, }, }WHIP 推流端点(whipHandler)
whipHandler 是整个推流链路的入口,处理流程如下:
- CORS 预检:响应头允许任意跨域来源、
POST方法与Authorization头;OPTIONS请求直接返回(main.go); - 读取 Offer:
io.ReadAll(req.Body)取出请求体中的 SDP; - 配置 MediaEngine:注册 H264(PayloadType 96、时钟 90000Hz)与 Opus(PayloadType 97、时钟 48000Hz、双声道)两种编解码(main.go);
- 组装拦截器:先注册
intervalpli.NewReceiverInterceptor()——该拦截器每 3 秒向推流端发送一次 PLI 请求,促使编码器生成关键帧,使视频"可 seek"且抗丢包,代价是画质与码率开销;随后调用RegisterDefaultInterceptors补齐 NACK、RTCP 收发报告、TWCC 发送端等默认拦截器(main.go)。从 RegisterDefaultInterceptors 的实现 可以看到,默认集合依次配置了 NACK 生成/应答、RTCP 收发报告、Simulcast 头扩展、统计拦截器与 TWCC 发送端; - 创建 API 与 PeerConnection:
webrtc.NewAPI(webrtc.WithMediaEngine(...), webrtc.WithInterceptorRegistry(...))以自定义 MediaEngine 与拦截器注册表构造 API 实例,再据此创建 PeerConnection(main.go)。注意:如果手动管理 MediaEngine,就必须为每个 PeerConnection 显式创建 InterceptorRegistry,而不能依赖webrtc.NewPeerConnection的默认行为; - 声明收发能力:
AddTransceiverFromKind为视频和音频各添加一个接收 Transceiver,表示允许接收一条视频轨道与一条音频轨道(main.go); - 转发媒体:在
OnTrack回调中启动两个 goroutine——一个持续调用receiver.ReadRTCP()消费推流端的 RTCP(读到io.EOF即会话结束);另一个持续调用track.ReadRTP()读取 RTP 包,并剥离 WHIP 扩展头(pkt.Header.Extensions = nil; pkt.Header.Extension = false)后按轨道类型写入videoTrack或audioTrack,从而完成向所有 WHEP 订阅者的广播(main.go); - 回送 Answer:调用
writeAnswer通过 HTTP 响应返回携带 ICE Candidate 的 SDP。
WHEP 订阅端点(whepHandler)
whepHandler 与推流端点的差异集中在两点:
- 拦截器仅使用默认集合,并额外调用
ConfigureTWCCHeaderExtensionSender(media, ir)为音视频轨道注册 Transport-CC(sdp.TransportCCURI)头扩展并挂载 TWCC 头扩展拦截器(main.go),目的是在订阅端支持 TWCC(Transport-wide Congestion Control)时提供带宽估计所需的传输级拥塞控制反馈。从源码看,该函数会向 MediaEngine 注册视频、音频两条头扩展并添加twcc.NewHeaderExtensionInterceptor()(interceptor.go); - 通过
peerConnection.AddTrack(videoTrack/audioTrack)把 WHIP 会话写入的全局轨道绑定到订阅连接,并为音视频各启动一个循环读取rtpSender.Read(rtcpBuf)的 goroutine,持续消费订阅端回传的 RTCP 包——这一步对 NACK 等反馈机制的生效是必需的(main.go)。
统一的 Answer 生成(writeAnswer)
writeAnswer 是 WHIP/WHEP 两端共用的核心逻辑,体现了该协议的关键约束——只能交换一次信令消息,因此必须禁用 Trickle ICE:
peerConnection.OnICEConnectionStateChange(func(connectionState webrtc.ICEConnectionState) { if connectionState == webrtc.ICEConnectionStateFailed { _ = peerConnection.Close() } }) peerConnection.SetRemoteDescription(webrtc.SessionDescription{ Type: webrtc.SDPTypeOffer, SDP: string(offer), }) gatherComplete := webrtc.GatheringCompletePromise(peerConnection) answer, err := peerConnection.CreateAnswer(nil) peerConnection.SetLocalDescription(answer) <-gatherComplete // 阻塞直至 ICE 收集完成 res.Header().Add("Location", path) res.WriteHeader(http.StatusCreated) // WHIP/WHEP 要求 201 状态码与 Location 头 fmt.Fprint(res, peerConnection.LocalDescription().SDP)其中GatheringCompletePromise返回一个"ICE 收集完成即关闭"的 channel(gathering_complete_promise.go),并处理了 promise 创建晚于收集完成导致永久阻塞的边界情况。<-gatherComplete确保 Answer 中已包含全部 ICE Candidate,这是"单次 HTTP 往返完成协商"的前提。注释也明确指出:生产环境中应通过OnICECandidate回调流式交换候选以缩短建连时间。协议层面,WHIP/WHEP 规范要求响应状态码为201 Created并携带Location头,示例分别回填/whip与/whep。
浏览器端:一次 HTTP POST 完成协商
doWHIP 与 doWHEP 展示了浏览器侧的对称实现:先createOffer并setLocalDescription,随后用fetch把offer.sdp以application/sdp类型POST到对应端点,响应体即为 SDP Answer,直接setRemoteDescription({ sdp: answer, type: 'answer' })。两处的差异仅在于:
- Publish(doWHIP):
addTrack把采集到的音视频轨加入连接,方向为发送; - Subscribe(doWHEP):
addTransceiver('video'/'audio', { direction: 'recvonly' })声明只收不发,并在ontrack中把收到的流挂到<video>元素播放。
页面还通过oniceconnectionstatechange实时把 ICE 连接状态(new/checking/connected 等)追加到页面下方,便于观察协商与建连过程。
生产化与扩展方向
示例代码在核心信令流程之外,还有几个值得在生产中补强的点(均为源码注释与实现明确提及):
- Bearer Token 鉴权:
index.html发送Authorization: Bearer none,但服务端未校验;真实部署应在whipHandler/whepHandler中解析并验证该头,防止任意客户端占用推流通道; - RTCP 反馈转发:当前方案由
intervalpli固定每 3 秒请求一次 PLI;源码注释(main.go)建议真实应用应解析订阅端的 RTCP 包并转发给推流端,实现按需关键帧与基于反馈的自适应码率; - ICE 候选流式交换:
GatheringCompletePromise会带来更长的启动等待时间(其文档注释明确说明),生产环境建议改用 Trickle ICE 逐条发送候选; - 媒体消费:
OnTrack中当前直接把 RTP 转发给 WHEP 订阅者,若要落盘录制或转码,可在读取 RTP 的分支中加入处理逻辑(仓库中的 save-to-disk 示例展示了如何把 RTP 写入 IVF 文件); - 编码协商扩展:MediaEngine 目前仅注册 H264 + Opus,需要支持 VP8/VP9/AV1 时,可在两处 handler 中参照相同模式追加注册。
至此,你已经用 Pion WebRTC 搭建起一条"OBS/浏览器推流 → Go 服务中转 → 浏览器/工具亚秒级订阅"的完整 WHIP/WHEP 直播链路,可以基于此继续扩展鉴权、录制、多订阅与码率自适应等能力。
- WebRTC
- 音视频
- 即时通讯
- 通信
【免费下载链接】webrtc
Pure Go implementation of the WebRTC API
相关推荐
ADK-Python 如何用 ServiceRegistry 在 services.py 注册自定义会话服务 URI 方案
ADK Python 如何用 ServiceRegistry 在 services.py 注册自定义会话服务 URI 方案 ADK 的 adk web 、 ad
WebRTC音视频即时通讯通信SRS 一键部署视频直播服务实战:OBS 推流、WebRTC 浏览器直播与 SRT 低延迟直播全指南
SRS 一键部署视频直播服务实战:OBS 推流、WebRTC 浏览器直播与 SRT 低延迟直播全指南 搭建一个真正可用的在线视频直播服务,远不止运行一个媒体服务
音视频后端直播SRS WebRTC 实战指南:WHIP/WHEP 推拉流、Candidate 配置与 SFU 架构详解
SRS WebRTC 实战指南:WHIP/WHEP 推拉流、Candidate 配置与 SFU 架构详解 导读 WebRTC 是 Google 开源的浏览器实时
音视频后端直播
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考