用一门还没多少人听说过的语言去重写 WebRTC 的传输层,然后让它和 Chrome 真正打通视频通话——这听起来像是在造火箭的同时还在发明新的焊接工艺。Uya WebRTC v0.3.0 就是这么干的:从只能跑 DataChannel 的验证阶段,一路推进到能解析 Chrome 的 video SDP、接收 SRTP/VP8 RTP 流,并在本地用 FFmpeg 手工完成和 Chrome 的视频互通。
从 DataChannel 到 Video:跨过的不是一道坎
v0.2 时代的 Uya WebRTC 只能跑 DataChannel,说白了就是「能传文字,不能传画面」。DataChannel 虽然也走 SCTP,但它不涉及媒体协商的核心复杂度——SDP 里没有 video m-line,不需要处理 SRTP 加密,不需要 RTP 包头的序列号和 timestamp 校验。
v0.3.0 把 PeerConnection 的通用性往前推了一大步:
- Chrome video SDP 解析:能正确理解 Chrome 发出的包含 video media section 的 Offer/Answer SDP,包括 ICE candidate 的捆绑和 mid 对齐。
- SRTP/VP8 RTP 接收路由:解密 SRTP 包后,按 VP8 payload type 分流到解码管线,而不是把所有 RTP 包当成 DataChannel 的 SCTP 片段。
- Host FFmpeg ↔ Chrome 手工互通:在本地环境用 FFmpeg 编码 VP8 流、通过 Uya WebRTC 的 transport 发出,Chrome 端能收到并渲染;反过来,Chrome 发出的视频流也能被 Uya 端接收并用 FFmpeg 解码验证。
这三件事合在一起,意味着 Uya 实现的 WebRTC transport 不再是「概念验证」,而是真的在协议层面和主流浏览器对上了表。
SDP 和 SRTP:两个最容易卡住的关卡
WebRTC 的 SDP 不是普通文本,它是一个不断膨胀的协商怪物。Chrome 生成的 video SDP 通常包含:
- 多个 m= 行(audio + video,有时还有 data)
- BUNDLE grouping,所有 media section 共用同一组 ICE candidate
- ICE-ufrag/ice-pwd 嵌在 session level 或 media level
- fingerprint 指明 DTLS 证书的 SHA-256
- extmap 映射 RTP header extension(abs-send-time、transport-cc 等)
Uya WebRTC v0.3.0 需要正确拆解这些结构,尤其是 BUNDLE——如果 mid 和 m-line 的对应关系搞错,ICE candidate 就会路由到错误的 media section,视频流根本到不了解码器。
SRTP 同样棘手。DTLS 握手完成后,双方协商出 SRTP 的 master key 和 salt,然后每一路 RTP 包都要用对应的 crypto context 做 encryption/authentication。Chrome 默认用 AES_CM_128_HMAC_SHA1_80(profile 0x01)。如果 SRTP 解密失败,最常见的症状是:包能收到,但解码器喂进去全是垃圾帧。
实操:用 FFmpeg 验证 Uya WebRTC 的视频接收
下面是一个最小化的验证流程,模拟「Uya 端接收 Chrome 发出的 VP8 流并用 FFmpeg 解码」的场景。假设你已经有了 Uya WebRTC 的 transport 层能输出解密后的 RTP 包。
第一步:抓取 SRTP 解密后的裸 RTP 流
Uya WebRTC transport 解密 SRTP 后,把裸 RTP 包写到本地文件或管道。这里假设输出到 /tmp/uya_rtp.bin:
# 如果 Uya transport 支持直接输出裸 RTP 到文件
# 启动 Uya WebRTC peer,接收 Chrome 的 video Offer
uya-webrtc --mode answer --sdp-file /tmp/chrome_offer.sdp \
--rtp-out /tmp/uya_rtp.bin --payload-type 96
第二步:用 FFmpeg 从裸 RTP 包解码 VP8
# 从裸 RTP 文件解码 VP8 并输出到 YUV 文件验证
ffmpeg -f rtp -i /tmp/uya_rtp.bin \
-c:v vp8 -pix_fmt yuv420p \
-f rawvideo /tmp/uya_video.yuv
# 或者直接播放(需要 X11 或 Wayland 显示环境)
ffmpeg -f rtp -i /tmp/uya_rtp.bin -c:v vp8 -f sdl "Uya WebRTC Test"
第三步:反向测试——Uya 端发视频给 Chrome
# 用 FFmpeg 编码测试源为 VP8 RTP 流
ffmpeg -f lavfi -i testsrc2=size=640x480:rate=30 \
-c:v vp8 -f rtp rtp://127.0.0.1:5004 \
-sdp_file /tmp/uya_video_sdp.sdp
# Uya WebRTC 把这个 RTP 流封装进 SRTP,通过 PeerConnection 发给 Chrome
uya-webrtc --mode offer --video-source rtp://127.0.0.1:5004 \
--payload-type 96 --sdp-out /tmp/uya_offer.sdp
在 Chrome 端,用 RTCPeerConnection 加载这个 Offer,设置 remote description 后,ontrack 事件应该能拿到 VP8 的 MediaStreamTrack 并渲染到 <video> 元素。
一个最简的 Chrome 端接收代码:
<script>
const pc = new RTCPeerConnection();
pc.ontrack = (e) => {
const video = document.getElementById('uyaVideo');
video.srcObject = e.streams[0];
};
// 假设从 Uya 端拿到了 offer SDP 文本
fetch('/uya_offer_sdp').then(r => r.text()).then(sdp => {
pc.setRemoteDescription({ type: 'offer', sdp }).then(() => {
return pc.createAnswer();
}).then(answer => {
return pc.setLocalDescription(answer);
}).then(() => {
// 把 answer SDP 发回 Uya 端
fetch('/uya_answer_sdp', {
method: 'POST',
body: pc.localDescription.sdp
});
});
});
</script>
<video id="uyaVideo" autoplay></video>
新语言做基础设施:收益与代价
用 Uya 重写 WebRTC transport,最直接的收益是语言层面的安全性和表达力。C/C++ 写的 libwebrtc 动辄百万行代码,内存安全漏洞和未定义行为是常态。如果 Uya 在语言设计上就排除了这类问题(比如有所有权机制或 GC),那 transport 层的可靠性会天然更高。
但代价也很真实:
- 生态空白:没有现成的 DTLS/SRTP/VP8 库可以直接调,要么自己实现,要么做 FFI 绑定到 C 库。v0.3.0 能跑通 VP8,大概率还是绑了外部编解码器。
- 性能未验证:WebRTC transport 对延迟极度敏感,RTP 包处理必须在微秒级完成。新语言的运行时开销(GC pause、JIT 冷启动等)在高压场景下表现如何,还需要实测。
- 兼容性长路:Chrome 的 WebRTC 实现每年都在加新特性(Simulcast、SVC、AV1 编码),Uya WebRTC 要跟上,工程量不小。
下一步可以关注什么
如果你对 Uya WebRTC 感兴趣,几个值得跟踪的方向:
- Audio m-line 互通——v0.3.0 只验证了 video,Opus 的音频协商和解码是下一个自然目标。
- ICE full mode——目前可能只支持 host candidate,STUN/TURN 的完整 ICE 流程还没覆盖。
- 自动化互通测试——手工用 FFmpeg 验证很硬核,但长期需要自动化 CI 流程,每次提交都跑 Chrome ↔ Uya 的回归测试。
- 性能基准——在 30fps/720p 的负载下,测量 Uya transport 的 RTP 处理延迟和 CPU 占用,和 libwebrtc 做对比。
新语言写基础设施,从来不是「能不能」的问题,而是「值不值得持续投入」的问题。v0.3.0 证明了 Uya 能啃下 WebRTC 视频互通这块硬骨头,接下来要看的是:它能不能跑得稳、跑得快、跑得久。