SDP 字段详解与 GB28181 实战:从抓包排查到媒体协商
SDP 这个东西第一次接触的人多半是在抓包的时候一脸懵明明 SIP 信令里 INVITE 已经发出去了怎么媒体流就是不通或者更常见的场景——对接某家平台时对方说你把 SDP 里的 IP 和端口填对就行结果填完了还是单向音频。这类问题的根子十有八九都落在 SDP 这个看似简单的文本块上。SDP 全称 Session Description Protocol直译就是会话描述协议。但严格来说它压根不是协议而是一种格式约定——用纯文本描述一次多媒体会话里谁跟谁通信、用什么地址、什么端口、什么编码、什么方向。它自己不做协商也不传数据真正干活的是 SIP、RTSP、HTTP 这些承载它的信令协议。GB28181 就是把这些东西组合起来做成了国内视频监控联网的一套标准玩法。这篇内容我打算把 SDP 从字段级别拆开讲透再落到 GB28181 对它的具体规定上。适合正在做视频监控对接、SIP 信令开发、或者被 SDP 里的cma搞得头大的朋友。看完你至少能做到拿到一段 SDP 能逐行读懂知道每个字段为什么这么填遇到媒体不通时知道从哪几个字段下手排查。1. 先把 SDP 的文本结构看明白1.1 它为什么长成一行一个字母的样子SDP 的格式非常朴素整个会话描述就是若干行文本每行格式固定为类型字母值。比如v0、maudio 5004 RTP/AVP 0。这种设计不是偷懒而是有历史原因的早期会话描述要在邮件、公告板这类纯文本环境里传递二进制编码容易在传输中被破坏纯文本则天然抗造人也能直接读。类型字母一共就那么十来个常用的更少。我列个表把最常打交道的几个字段和它们的含义、出现顺序说清楚字段名称是否必选作用v版本必选目前固定为 0o发起者必选会话发起者标识与版本号s会话名必选会话名称无实际意义但不能省c连接信息可选媒体连接地址会话级或媒体级t时间必选会话起止时间m媒体描述必选媒体类型、端口、传输协议、格式a属性可选各种扩展属性最灵活也最容易出错顺序上有个硬性规矩v必须第一行o第二s第三t在m之前。c可以出现在会话级所有媒体共用也可以出现在媒体级每个媒体单独指定。这个顺序不是建议是规范强制写反了对方解析器可能直接丢弃整段 SDP。1.2 会话级描述和媒体级描述的分工一段完整的 SDP 通常分成两大块。第一块是会话级从v到第一个m之前描述的是这次会话整体是什么。第二块是媒体级从每个m开始描述这一路媒体具体怎么传。一个会话可以有多路媒体比如一路视频加一路音频就有两个m段。这里有个特别容易踩的坑c连接信息如果写在会话级那所有媒体都用这个地址如果某路媒体需要单独的地址就得在对应的媒体级再写一个c它会覆盖会话级的设置。我在实际对接中见过有人会话级写了一个内网地址媒体级又忘了覆盖结果视频流全往内网发外网根本收不到。举个真实会遇到的例子一段典型的 GB28181 视频 SDP 长这样v0 o34020000001320000001 0 0 IN IP4 192.168.1.100 sPlay cIN IP4 192.168.1.100 t0 0 mvideo 5004 RTP/AVP 96 98 97 arecvonly artpmap:96 PS/90000 artpmap:98 H264/90000 artpmap:97 MPEG4/90000 y0100000001 f逐行读下来版本 0发起者是某设备编号 34020000001320000001会话名叫 Play连接地址是 192.168.1.100时间从 0 到 0 表示持久会话媒体是视频、端口 5004、用 RTP/AVP 传输、支持 96/98/97 三种负载类型属性里声明了只收不发、三种编码的映射关系最后y和f是 GB28181 特有的扩展字段。1.3 o 行里那个版本号为什么不能乱填o行格式是o用户名 会话ID 会话版本 网络类型 地址类型 地址。很多人写 SDP 时随手把会话版本填 0觉得无所谓。但在 SIP 场景里这个版本号是有实际作用的当同一个会话的描述需要更新时比如重新协商媒体版本号必须递增接收方靠它判断这是新描述还是旧描述。如果版本号不变某些实现会认为收到了重复的旧 SDP 而直接忽略导致重新协商失败。我遇到过重新 INVITE 时媒体参数改了但版本号没动对方一直用旧参数回包排查了半天才发现是这个字段的问题。所以记住每次修改 SDP 内容会话版本号加一这是最省事的做法。2. m 行才是媒体协商的主战场2.1 m 行每个字段拆开看m行的完整格式是m媒体类型 端口 传输协议 格式列表。以mvideo 5004 RTP/AVP 96 98 97为例video是媒体类型常见的有 audio、video、text、application、message。5004是接收媒体的端口。注意这是接收端口不是发送端口。发送端口由对端在它的 SDP 里告诉你。RTP/AVP是传输协议表示用 RTP 承载、走 AVPAudio/Video Profile规范。GB28181 里基本都是这个。96 98 97是负载类型编号列表也就是 payload type。这些数字本身没有固定含义具体代表什么编码要靠artpmap去映射。这里有个关键点96 到 127 是动态负载类型含义由artpmap现场定义而 0 到 95 是静态的比如 0 就是 PCMU 音频、8 是 PCMA。GB28181 里视频编码基本都用动态类型因为 PS 封装、H264 这些没有预分配的静态编号。2.2 端口填 0 意味着什么m行里的端口如果填 0含义是这一路媒体被拒绝/不启用。这是 SDP 协商里一个很实用的机制。比如主叫方发来视频和音频两路被叫方只想接视频那它在回包里把音频那行的端口写成 0主叫方一看就知道音频被拒了不用再发。这个机制在排查问题时特别有用。如果你看到对方回的 SDP 里某路媒体端口是 0别怀疑是配置错了那就是明确告诉你这路我不接。反过来如果你自己不想接某路媒体也应该主动把端口置 0而不是直接删掉那行——删掉可能让对方以为你没收到置 0 才是标准拒绝方式。2.3 多路媒体时的端口分配逻辑一个会话里有多路媒体时端口不能重复。常见做法是视频用 5004、音频用 5006或者干脆连续分配。但要注意RTP 和 RTCP 通常成对出现RTP 用偶数端口RTCP 用相邻的奇数端口比如 RTP 5004、RTCP 5005。虽然 SDP 里只写了 RTP 端口但实际传输时 RTCP 会占用下一个端口防火墙放行时两个都要开。我在实际部署中踩过一次坑只放行了 5004结果 RTP 通了但 RTCP 被挡导致丢包统计、抖动计算全失效画面能看但质量监控完全瞎了。所以端口规划时永远按一对来考虑别只盯着 SDP 里写的那个数字。3. a 属性行灵活到让人又爱又恨3.1 rtpmap 与 fmtp 的配合关系artpmap负责把负载类型编号映射到具体编码格式是artpmap:负载类型 编码名/时钟频率[/声道数]。比如artpmap:96 PS/90000表示负载类型 96 是 PS 封装、时钟频率 90000Hz。视频编码的时钟频率基本都是 90000音频常见 8000 或 16000。afmtp则是给某个编码传额外参数格式是afmtp:负载类型 参数。比如 H264 常用afmtp:98 profile-level-id42e01e; packetization-mode1来指定档次和打包模式。这两个属性是绑定的先有 rtpmap 定义编码再有 fmtp 补充细节。GB28181 里有个特殊约定PS 封装的负载类型通常固定用 96H264 用 98MPEG4 用 97但这只是常见实践不是强制标准。真正决定含义的还是 rtpmap 那行。所以对接时不要靠负载类型编号猜编码一定要看 rtpmap。3.2 sendrecv、recvonly、sendonly 的方向语义a行里控制媒体方向的属性有三个asendrecv收发都行、arecvonly只收、asendonly只发。默认如果不写就是 sendrecv。在 GB28181 的点播场景里这个方向语义特别重要。当客户端向设备请求视频时客户端发的 SDP 里通常写arecvonly意思是我只收不发设备回的 SDP 里写asendonly意思是我只发不收。两边方向对上媒体才能正常流动。如果方向写反了比如两边都写 recvonly那就是互相等着对方发结果谁都不发媒体流自然不通。我见过最隐蔽的一次故障就是方向属性不匹配信令全通、端口全对就是没画面最后发现是设备端把 sendonly 写成了 recvonly。3.3 GB28181 特有的 y 和 f 字段严格说y和f不是标准 SDP 的a属性而是 GB28181 在 SDP 里扩展的独立字段。y是 SSRC同步源标识用来标识这一路 RTP 流的唯一性格式是十位十进制数字。f是媒体流描述通常为空或带特定标识。SSRC 在 GB28181 里作用很大它让接收端能区分同一端口上可能存在的多路流也是很多平台做流关联的依据。填 SSRC 时有固定规则前几位通常包含设备类型、区域编码等信息具体规则要看对接平台的文档。我建议SSRC 一定要按平台要求填不要随便编因为有些平台会校验它的合法性填错了直接拒绝。4. GB28181 对 SDP 的具体规定与实战差异4.1 点播、回放、下载三种场景的 SDP 区别GB28181 里不同的业务场景SDP 的写法有细微但关键的差别。点播实时视频时s字段通常是Play媒体方向是设备 sendonly、客户端 recvonly。回放历史录像时s是Playback并且会带arange之类的属性来指定时间范围。下载场景则可能用不同的传输方式。这些差别看似小但对接时如果场景判断错了平台可能直接返回错误。比如把回放的 SDP 当成点播发设备不知道你要哪段录像就会拒绝或者返回实时流。所以发 SDP 前先确认业务类型这是最基本的。4.2 编码协商PS 封装为什么是主流GB28181 规定视频媒体优先使用 PSProgram Stream封装负载类型 96。为什么用 PS 而不是直接传 H264 裸流因为 PS 封装能把视频、音频、甚至私有数据打包在一起还能携带时间戳信息适合监控这种多路复用、需要精确同步的场景。实际对接中有些设备同时支持 PS 和裸 H264SDP 里会列出多个负载类型。这时候接收端选哪个取决于它自己支持什么。如果接收端只认 PS那它应该在回包里只保留 96把其他删掉或置 0。这个协商过程如果处理不好就会出现设备发了 H264 裸流平台只认 PS结果解不出来的情况。4.3 对接中最常见的 SDP 不匹配问题我把这些年遇到的 SDP 相关问题归了几类列个表方便对照排查现象可能原因排查方向信令通但无媒体c 地址不可达检查连接地址是否本机可达单向音频/视频方向属性不匹配核对 sendrecv/recvonly/sendonly媒体端口不通端口未放行或填 0检查 m 端口与防火墙编码解不出rtpmap 与 fmtp 不符核对负载类型映射重新协商失败o 版本号未递增检查会话版本号流无法关联SSRC 缺失或错误核对 y 字段规则这张表基本覆盖了八成以上的 SDP 对接故障。排查时按地址→端口→方向→编码→版本→SSRC的顺序走效率最高。5. 手把手构造一段合规的 GB28181 SDP5.1 从零写一段点播请求 SDP假设客户端要向设备请求实时视频客户端自己的地址是 192.168.1.200想用 5004 端口收流。一段合规的请求 SDP 可以这样写v0 o34020000002000000001 0 0 IN IP4 192.168.1.200 sPlay cIN IP4 192.168.1.200 t0 0 mvideo 5004 RTP/AVP 96 arecvonly artpmap:96 PS/90000 y0100000001 f关键点c写客户端自己的地址因为客户端要收流m端口写客户端准备接收的端口方向写recvonly负载类型只列 96PSSSRC 按平台规则填。5.2 设备应答 SDP 应该怎么改设备收到请求后回的 SDP 要把方向反过来地址和端口改成设备自己发流用的v0 o34020000001320000001 0 0 IN IP4 192.168.1.100 sPlay cIN IP4 192.168.1.100 t0 0 mvideo 5004 RTP/AVP 96 asendonly artpmap:96 PS/90000 y0100000001 f注意这里m的端口 5004 是设备发送用的端口不是接收端口。客户端收到后就往 192.168.1.100:5004 发 RTCP、收 RTP。方向变成sendonly表示设备只发。5.3 用抓包验证 SDP 是否生效写完 SDP 别急着上线抓个包验证最靠谱。用抓包工具过滤 SIP 信令找到 INVITE 和 200 OK把里面的 SDP 段复制出来逐行核对。重点看三处c地址是不是对方能到的地址、m端口是不是放行了、方向属性是不是互补。如果抓包看到 SDP 发出去了但对方没回先检查o和s这些必选字段有没有缺。有些解析器对缺失必选字段是零容忍的直接静默丢弃连错误都不报特别难查。6. 几个只有踩过才知道的 SDP 细节6.1 换行符和空格这些看不见的坑SDP 每行必须以 CRLF\r\n结尾不是单纯的\n。有些实现对此很严格用\n会导致解析失败。另外字段之间的空格数量也有讲究虽然多数解析器能容忍多空格但标准是单空格分隔。我在跨平台对接时遇到过 Windows 端生成 SDP 用\r\n、Linux 端用\n结果一边能解析一边不能的情况。提示生成 SDP 时统一用 CRLF并且确保最后一行也有换行符。这个细节在文本编辑器里看不出来但抓包一看十六进制就露馅了。6.2 会话级 c 和媒体级 c 的覆盖陷阱前面提过c可以出现在两个层级媒体级覆盖会话级。但很多人不知道的是如果会话级没有c那每个媒体级都必须有否则那段媒体就没有连接地址直接失效。这个规则在只有一路媒体时容易忽略因为大家习惯把c写在会话级。我的建议是要么统一写在会话级要么每路媒体都写媒体级不要混着来。混用最容易出现某路媒体漏写c的情况而且这种错误在信令层面看不出来只有媒体不通时才暴露。6.3 时钟频率填错导致的音视频不同步artpmap里的时钟频率决定了 RTP 时间戳的换算基准。视频用 90000 是行业惯例音频用 8000 或 16000。如果时钟频率填错比如视频填了 8000那接收端按 8000 去换算时间戳播放速度就会完全乱掉表现为快放或慢放。这个问题在纯视频场景下可能不明显但一旦有音频音视频同步就会崩。所以时钟频率一定要和编码标准匹配H264/H265 用 90000G711 用 8000G722 用 16000这些是固定的别自己发挥。6.4 重新协商时哪些字段必须变、哪些不能变重新协商re-INVITE时o行的会话版本号必须递增这是硬性要求。m行的端口如果变了也要同步更新。但o行的会话 ID 通常保持不变用来标识还是同一个会话。如果把会话 ID 也改了对方可能认为是全新会话导致旧的媒体资源没释放、新的又建起来端口冲突。我个人的做法是重新协商只动版本号、必要的端口和编码列表其他字段尽量保持稳定。改动越少出问题的概率越低。7. 排查 SDP 问题的通用思路7.1 先确认信令层还是媒体层的问题遇到媒体不通第一步是分清问题出在信令还是媒体。判断方法很简单看 SIP 交互是否完整走完INVITE→200 OK→ACK。如果信令都没走完那跟 SDP 内容关系不大是信令层的问题。如果信令完整走完了但没媒体那才轮到查 SDP。这个分界线很重要因为很多人一上来就盯着 SDP 看结果发现是 SIP 注册都没成功。先分层再定位能省大量时间。7.2 用最小化 SDP 做二分排查当 SDP 复杂到看不清问题时可以做个最小化版本只保留必选字段和一路媒体把可选属性全删掉看能不能通。能通再逐步加回属性加到哪一步不通问题就在那个属性上。这是最笨但最有效的二分法。我处理过一个案例SDP 里有七八个a属性怎么调都不通。最后用最小化版本一试就通了然后一个个加回去发现是某个afmtp参数格式不对导致整段被拒。如果一开始就盯着那一堆属性看很难定位到具体是哪个。7.3 对照标准逐字段核对的检查清单最后给一份我常用的核对清单对接新平台时按这个过一遍基本不会漏v是否为 0是否在第一行o三个字段用户名、会话ID、版本号是否齐全版本号是否合理s是否存在哪怕是空字符串也要有这行c地址是否本机可达层级是否正确t是否存在m端口是否放行协议是否为 RTP/AVPartpmap是否覆盖了m里列出的所有负载类型方向属性是否与对端互补ySSRC 是否符合平台规则换行符是否为 CRLF这份清单看着琐碎但每一条我都见过真实故障。SDP 这东西就是这样格式简单到让人轻视细节却多到能坑死人。把字段吃透、把协商逻辑理顺剩下的就是耐心核对了。