

想象一下,当你和一位外国朋友初次见面,你们会如何开始交流?大概率会先试探性地问一句:“Can you speak English?” 或者 “你会说中文吗?” 这个过程,其实就是在“协商”你们接下来沟通要使用的“媒体”——语言。在WebRTC的世界里,两个准备进行实时音视频通话的客户端,也需要这样一个“破冰”环节,而它们用来沟通的语言,就是我们今天要聊的主角——SDP(Session Description Protocol,会话描述协议)。它就像一张详细的个人说明书,告诉对方“我能做什么”以及“我希望怎么做”,从而为顺畅的实时通信铺平道路。
在我们深入探讨SDP如何进行媒体协商之前,有必要先弄清楚它的真实身份。很多人可能会被它的名字“协议”所误导,但实际上,SDP本身并不是一个传输协议,它更像是一种标准化的“描述格式”。
SDP最早由IETF(互联网工程任务组)定义,其规范随着技术发展不断演进,目前最新的版本是RFC 8866。它的设计初衷就是为了描述多媒体会话,用于会话的宣告、邀请和其它形式的发起。你可以把它理解成一份结构化的纯文本信息,由多行<type>=<value>这样的键值对组成,每一行都清晰地定义了会话的某个方面,比如时间、媒体类型、传输地址等。
这种简洁明了的文本格式,使得它非常易于解析和生成,具备极佳的扩展性。虽然诞生于WebRTC之前,但正是这些优点,让它成为了WebRTC技术栈中不可或的一部分,专门承担起媒体能力协商的重任。
在WebRTC的通信场景中,SDP的角色尤为关键。当两个对等端(Peer)准备建立连接时,它们对彼此的能力一无所知:对方支持哪些音视频编解码器?使用哪个IP地址和端口接收数据?加密方式是什么?所有这些问题,都必须在数据传输开始前得到解答。SDP就是承载这些答案的载体。

这个过程遵循一个经典的“Offer/Answer”(提议/应答)模型。一方(通常是呼叫方)首先创建一个“Offer” SDP,里面包含了自己所支持的所有媒体能力和偏好设置。另一方(被叫方)收到后,会根据自身情况生成一个“Answer” SDP作为回应,这个Answer里包含了双方能力的“交集”。通过这样一问一答,双方就达成了一个通信的“君子协定”,明确了接下来要使用的具体参数。
“Offer/Answer”模型是SDP协商的核心机制,它像一场精心设计的双人舞,每一步都有其明确的规则和目的,最终目的是让两个独立的端点能够和谐地“共舞”。
一切始于一个“Offer”。当一个用户(我们称之为Alice)想要呼叫另一个用户(Bob)时,Alice的浏览器会通过WebRTC API中的createOffer()方法,生成一份详细的SDP Offer。这份Offer里包含了Alice这边所有的“家底”,具体来说有:
m=行中定义。

这份Offer生成后,需要通过一个信令(Signaling)服务器发送给Bob。WebRTC本身不规定信令的实现方式,你可以用WebSocket、HTTP或者任何其他方式来传递它。
Bob收到Alice的Offer SDP后,并不会全盘接受。他需要检查Alice的“家底”,并与自己的能力进行匹配。这个过程就像是在Alice开出的长长菜单上,勾选出自己也能做并且愿意吃的菜。
Bob的浏览器会调用createAnswer()方法,生成一份Answer SDP。这份Answer是基于Offer创建的,它代表了双方协商的结果:
Bob将这份Answer SDP通过信令服务器回传给Alice。至此,媒体协商的核心环节就完成了。双方都清楚了对方的能力,并确定了通信所用的具体参数,接下来就可以开始尝试建立真正的P2P连接了。
一份SDP文档看起来可能有些神秘,但只要理解了其中关键字段的含义,就能轻松读懂这场“媒体对话”。SDP的内容分为会话级描述和媒体级描述两部分。
会话级描述应用于整个会话,通常位于SDP的开头部分。例如:

v=0:表示SDP的版本号。o=- 4611731400430051336 2 IN IP4 127.0.0.1:定义了会话的发起者,包含用户名、会话ID、版本、网络类型、地址类型和地址。s=-:会话名称。t=0 0:会话的起止时间,0 0表示永久。媒体级描述则针对特定的媒体流(音频或视频),以m=行开始。例如:m=audio 54312 UDP/TLS/RTP/SAVPF 111 103 104。这行信息告诉我们:这是一个音频(audio)流,期望在端口54312接收数据,使用的传输协议是安全的实时传输协议(RTP/SAVPF),并且支持的负载类型(payload types)是111、103和104。每个负载类型都对应一个具体的编解码器。
SDP中最重要、最灵活的部分,莫过于以a=开头的属性行。它们为媒体级描述提供了丰富的补充信息,是协商细节的关键。下面我们通过一个表格来了解几个核心的a=属性:
| 属性 | 示例 | 描述 |
|---|---|---|
| rtpmap | a=rtpmap:111 opus/48000/2 |
将负载类型111映射到具体的编码格式Opus,并指定了时钟频率(48000Hz)和声道数(2)。 这是将数字代号与实际编解码器关联起来的桥梁。 |
| fmtp | a=fmtp:111 minptime=10;useinbandfec=1 |
为特定格式(format-specific parameters)提供更详细的参数。 这里为Opus编码设置了最小打包时间,并启用了带内前向纠错(FEC)以增强抗丢包能力。 |
| candidate | a=candidate:1 1 UDP 2122252543 192.168.1.10 54312 typ host |
一个ICE候选地址。 它详细描述了连接类型(UDP)、优先级、IP地址、端口以及候选类型(host表示是本机地址)。 |
| fingerprint | a=fingerprint:sha-256 B7:4A:2A... |
DTLS证书的指纹。 通信双方会校验对方的证书指紋,确保没有中间人攻击,保证通信的机密性和完整性。 |
| setup | a=setup:actpass |
定义了在DTLS握手过程中的角色。 actpass表示该端点既可以作为主动方(client)也可以作为被动方(server),由对端来决定。 |
虽然SDP的协商机制是标准化的,但在复杂的真实网络环境中,尤其是在构建大规模、高可用的实时互动应用时,对SDP的处理和优化就显得尤为重要。像声网这样的专业实时音视频服务商,会在幕后做大量工作来简化和增强这个过程。例如,通过智能的信令服务器和媒体服务器,声网可以动态地调整SDP内容,以适应不同的网络状况和设备能力,确保在弱网环境下也能快速建立连接并保持高质量通信。此外,对于需要多方通话的复杂场景,声网的后端服务能够高效地管理和分发SDP,屏蔽底层实现的复杂性,让开发者可以更专注于业务逻辑的创新。
首次的Offer/Answer交换并非一成不变。在一次持续的通话中,情况随时可能发生变化,SDP协商机制也支持这种动态性,允许会话参数在进行中被修改。
想象一下,在一次视频通话中,你突然想分享你的屏幕。这个操作就需要一次“再协商”(Renegotiation)。此时,你的客户端会创建一个新的Offer,这个Offer会包含一个新的视频媒体流(代表屏幕分享)。这个新的Offer会再次通过信令服务器发送给对方,对方收到后生成Answer,从而完成媒体流的添加。
同样,关闭摄像头、切换前后摄像头、调整视频分辨率等操作,都可能触发一次再协商。这种灵活性是WebRTC能够适应各种复杂互动场景的关键。整个过程依然遵循Offer/Answer模型,只不过是在一个已经建立的连接上进行的更新。
在早期的WebRTC实现中,一方必须收集完自己所有的ICE candidate后,才能将包含完整地址列表的SDP发送出去。这个过程可能会很耗时,因为获取公网地址(STUN)和中继地址(TURN)都需要网络请求,从而导致通话建立的延迟,给用户“正在连接…”的等待感。
为了解决这个问题,“Trickle ICE”技术应运而生。它允许SDP的交换和ICE candidate的收集并行进行。具体来说,一方可以先发送一个不包含任何candidate的初始SDP,以尽快完成媒体参数的协商。然后,每当发现一个新的candidate时,就通过信令服务器将它“涓涓细流”(Trickle)般地单独发送给对方。这种方式大大缩短了首次出画、出声的时间,极大地提升了用户体验。对于追求极致实时体验的平台,如声网,充分利用Trickle ICE是保证其服务低延迟、高效率的基础。
WebRTC中的SDP媒体协商,虽然技术细节繁多,但其核心思想却十分直观:通过一次结构化的“对话”,让通信双方在正式开始之前,就媒体格式、网络路径和安全措施达成一致。这个过程以Offer/Answer模型为骨架,以SDP这种描述性语言为血肉,通过信令服务器进行传递,最终构建起一座可靠的实时通信桥梁。
从最初的会话描述,到关键的编解码器与网络地址交换,再到支持通话中动态调整的再协商机制,SDP的每一个设计都体现了其灵活性和可扩展性。正是有了这样一套强大的协商机制,WebRTC才能在不同设备、不同网络环境下,实现高质量、低延迟的音视频互动。
对于开发者而言,虽然像声网这样的平台已经将大部分SDP的复杂性封装起来,但深入理解其工作原理,仍然有助于我们更好地排查问题、进行性能优化,并最终打造出更富想象力的实时互动应用。未来,随着WebRTC技术的不断演进,SDP本身或许会演变,但其背后“先协商、后通信”的核心理念,将继续作为实时互联网的基石之一,连接着世界的每一个角落。

