WebRTC的协商过程依赖于SDP(Session Description Protocol)在Offer与Answer之间交换媒体配置。一份典型的SDP包含会话级信息、多个媒体段、编解码器优先级、ICE候选地址等,纯文本阅读门槛较高。在多人会议或复杂拓扑中,仅凭肉眼比对两端SDP是否匹配,效率很低。本文介绍的方案是:用Node.js解析SDP文本,提取结构化字段,再以图形方式输出一张Mock图片,直观展示媒体流方向、编解码器优先级和ICE状态。

SDP文本结构与解析方案
SDP是一种基于文本的协议,由多个行组成,每行以单个字符标识类型。常见的类型包括v(版本)、o(会话发起者)、s(会话名称)、c(连接信息)、m(媒体描述)、a(属性)。其中m行定义媒体类型、端口、传输协议和编解码器列表,a行承载RTCP、ICE候选、指纹等扩展属性。理解这些字段的层级关系,是正确解析SDP的前提。例如,每个m行之后跟随的a=rtpmap行用于将RTP负载类型映射到具体编解码器名称,而a=ice-ufrag和a=ice-pwd则标识该媒体流的ICE凭据。
在Node.js中处理SDP,推荐使用sdp-transform库,它能把纯文本转换为嵌套对象,也能反向序列化。安装命令为npm install sdp-transform。该库会把每个媒体段解析为包含type、port、protocol、rtp、fmtp、candidates等属性的对象,极大简化了后续的数据提取工作。下面是一个解析示例,将SDP文本转为可遍历的结构,为后续可视化做准备。
const sdpTransform = require('sdp-transform');
const rawSdp = `v=0
o=- 461173140 2 IN IP4 127.0.0.1
s=-
t=0 0
m=audio 9 UDP/TLS/RTP/SAVPF 111 103
a=rtpmap:111 opus/48000/2
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:abcd
a=ice-pwd:efgh
m=video 9 UDP/TLS/RTP/SAVPF 96
a=rtpmap:96 VP8/90000
a=rtcp-fb:96 nack
a=ice-ufrag:ijkl
a=ice-pwd:mnop`;
const parsed = sdpTransform.parse(rawSdp);
console.log(parsed.media.length); // 2
console.log(parsed.media[0].type); // audio
console.log(parsed.media[0].rtp[0].codec); // opus
生成可视化图片的两种常见方案
第一种方案是直接生成SVG(Scalable Vector Graphics)。SDP解析后的媒体信息可以通过坐标计算绘制成带箭头的媒体流拓扑。SVG作为文本格式,在Node.js中拼字符串即可生成,再使用sharp或resvg等工具将SVG渲染为PNG图片。这种方式对轻量级可视化需求非常友好,且无需安装Canvas相关的二进制依赖。同时SVG便于二次编辑和嵌入网页,适合作为调试报告中的插图。
第二种方案是使用node-canvas模拟浏览器Canvas API,适合需要复杂绘制逻辑或已有前端绘制代码复用的情况。缺点是安装时需要系统级图像库支持,在部分CI环境中可能失败。若只是生成静态Mock图片,SVG方案的性价比更高。下面给出一个构建SVG字符串的函数示例,它根据解析后的媒体列表生成基础矩形节点。
function buildSvg(parsed) {
const items = parsed.media.map((m, i) => {
const codec = m.rtp && m.rtp[0] ? m.rtp[0].codec : 'unknown';
return `<rect x="20" y="${40 + i * 60}" width="120" height="40" rx="6" fill="#e3f2fd" stroke="#1976d2"/>
<text x="30" y="${58 + i * 60}" font-size="14">${m.type}: ${codec}</text>`;
}).join('');
return `<svg xmlns="http://www.w3.org/2000/svg" width="400" height="${40 + parsed.media.length * 60 + 20}">
<rect width="100%" height="100%" fill="white"/>
${items}
</svg>`;
}
自动化生成脚本的完整实现
接下来把解析和可视化流程串起来,写成一个命令行脚本。脚本接收SDP文本文件路径,输出PNG图片。借助sharp库可以将SVG缓冲区直接渲染成PNG,无需引入浏览器环境。安装依赖:npm install sdp-transform sharp。该脚本遍历每个媒体段,提取媒体类型、编解码器列表、传输协议以及是否包含ICE凭据,然后生成垂直排列的卡片式布局。
以下脚本实现了从读取SDP到输出PNG的完整流程。为了便于阅读,我们按媒体顺序垂直排列节点,每个节点展示核心协商参数。这样生成的Mock图片可以快速对比Offer与Answer的差异,尤其适合多人会议场景下检查媒体流是否缺失。
const fs = require('fs');
const sdpTransform = require('sdp-transform');
const sharp = require('sharp');
async function renderSdpToImage(inputFile, outputFile) {
const rawSdp = fs.readFileSync(inputFile, 'utf8');
const parsed = sdpTransform.parse(rawSdp);
const rows = parsed.media.map((m, i) => {
const codecs = (m.rtp || []).map(r => r.codec).join(',');
const iceCount = (m.icePwd ? 1 : 0); // 简化判断
return `<g transform="translate(0,${i * 70})">
<rect x="10" y="10" width="360" height="50" rx="8" fill="#f5f5f5" stroke="#333"/>
<text x="20" y="32" font-size="14" font-weight="bold">${m.type}</text>
<text x="90" y="32" font-size="13">codecs: ${codecs}</text>
<text x="20" y="50" font-size="12">proto: ${m.protocol} | ice: ${iceCount}</text>
</g>`;
}).join('');
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="400" height="${40 + parsed.media.length * 70}">
<rect width="100%" height="100%" fill="white"/>
${rows}
</svg>`;
await sharp(Buffer.from(svg)).png().toFile(outputFile);
console.log(`Image saved to ${outputFile}`);
}
renderSdpToImage(process.argv[2], process.argv[3]).catch(err => {
console.error(err);
process.exit(1);
});
常见坑与优化建议
实际WebRTC SDP中经常出现BUNDLE、RTP多路复用等特性,同一个传输通道可能承载多个媒体类型,这时直接按媒体行计数会出现重复的拓扑节点。解析时应优先检查a=group:BUNDLE行,若音频、视频、数据共用同一个端口和ICE候选,在图上应合并展示。否则生成的图片会误导阅读者以为存在多条独立传输链路,与真实网络拓扑不符。
另一个容易忽略的是ICE候选的展示。原始SDP中的a=candidate行数量较多,把它们全部画在图上会让图片变得拥挤。建议只统计候选类型(host、srflx、relay)和优先级范围,或者用聚合色块表示。对于需要完整候选列表的场景,可以生成缩略图附带一个候选明细表格,这样主图保持简洁,细节信息也能完整保留。
性能方面,如果要对大量SDP会话做批量出图,建议将SVG渲染步骤放在子进程或使用worker线程,避免sharp的CPU密集操作阻塞主事件循环。同时生成的PNG可以缓存到本地,相同SDP哈希直接复用结果。此外,解析SDP时应对畸形格式做容错处理,例如缺少必需字段或行尾出现多余空格,避免异常导致整个出图任务中断。
这套方案已经在日常WebRTC信令调试中实际使用,将原来需要对照文本逐一比对的流程缩短为一眼扫图。你可以根据业务需要扩展节点颜色、连线箭头、媒体方向标注等细节,让Mock图片更贴近真实拓扑。