GB28181中SDP会话描述协议详解与对接排查指南
1. 从一个实际对接问题说起为什么SDP值得单独拎出来讲但凡做过视频监控平台对接、流媒体信令交互、或者安防设备联网的同行大概率都经历过这样的场景设备注册上来了信令也通了但一到点播就黑屏抓包一看SDP里的IP地址写的是内网地址或者媒体端口对不上又或者编码格式协商失败。这类问题排查起来往往很折磨人因为SDP本身看起来就是一段纯文本字段也不多但每一个字段背后都牵扯到会话描述、媒体协商、网络地址转换等一系列环节。SDP全称Session Description Protocol会话描述协议它本身并不传输媒体数据也不负责建立连接它做的事情只有一件用一套约定好的文本格式把一次会话里涉及到的媒体信息描述清楚。谁跟谁通信、用什么地址、什么端口、什么编码、什么传输协议、会话持续多久这些信息全部塞在一段SDP里。你可以把它理解成一张“媒体说明书”双方在正式传数据之前先交换这张说明书确认彼此能不能对上。而GB28181是国内视频监控联网领域的一套重要标准体系它规定了设备、平台之间如何注册、如何信令交互、如何点播回放。GB28181在会话描述这块直接采用了SDP作为载体但又不是原封不动地照搬而是结合监控场景做了大量约束和扩展。这就导致一个现象很多人学过通用SDP但一到GB28181的实际对接里还是会踩坑因为GB28181对SDP的字段取值、必选可选、编码规则都有自己的规定。这篇内容适合几类人看一是刚接触视频监控平台对接的开发者需要把SDP和GB28181的关系理清楚二是做流媒体网关、信令服务的中高级工程师需要一份能直接对照排查的参考三是做设备端固件、平台端信令模块的同行想搞清楚字段到底该怎么填才不会被对端拒绝。我会从SDP的通用结构讲起再逐层拆到GB28181对它的具体规定中间穿插实际对接中遇到的典型问题和处理思路尽量做到看完就能上手对照。2. SDP通用结构拆解一张媒体说明书到底写了什么2.1 SDP的整体格式与行类型SDP的本质是一段纯文本由若干行组成每行格式固定为类型字母值比如v0、maudio 49170 RTP/AVP 0。类型字母是单个字符区分大小写值的内容根据类型不同而不同。整个SDP由若干“行”构成行的顺序也有讲究一般按照会话级描述在前、媒体级描述在后的顺序排列。SDP的行分为两大类会话级描述和媒体级描述。会话级描述从v开始一直到第一个m之前描述的是整个会话的公共信息比如协议版本、会话名称、会话时间、连接信息等。媒体级描述从第一个m开始每个m行开启一个媒体描述块后面跟着属于这个媒体的属性行比如artpmap、afmtp等。一个SDP里可以有多个媒体描述块比如同时描述音频和视频。这里有个容易忽略的点会话级的c连接信息和媒体级的c连接信息是两回事。如果会话级写了c媒体级没写那媒体级就继承会话级的连接信息如果媒体级自己写了c那就以媒体级的为准。这个继承关系在实际排查中非常关键很多“地址不对”的问题就出在这里。2.2 必选字段与可选字段的区分SDP规范里对字段有“必选”和“可选”的区分但要注意这个必选是相对于SDP规范本身而言的。在实际的协议体系里比如GB28181可能会对某些字段提出额外的强制要求也可能允许某些字段省略。按照通用SDP规范会话级必选的字段包括v、o、s、t媒体级必选的是m。其他字段如i、u、e、p、c、b、z、k、a等都是可选的。但在实际使用中c几乎是必须的因为没有连接信息就无法知道媒体往哪里发。o字段值得单独说一下它的格式是o用户名 会话ID 会话版本 网络类型 地址类型 地址。会话ID和会话版本在GB28181里经常被用来做会话标识和版本控制如果对端发现会话版本没有递增可能会认为这是一个重复的请求而直接忽略。这个细节在后面讲GB28181规定时还会展开。2.3 媒体描述行的关键参数m行是媒体描述的核心格式是m媒体类型 端口 传输协议 格式列表。媒体类型常见的有audio、video、text、application等。端口是接收媒体流的端口号。传输协议常见的有RTP/AVP、RTP/SAVP等GB28181场景下基本都是RTP/AVP。格式列表是负载类型编号比如0代表PCMU8代表PCMA96、97、98等动态负载类型需要配合artpmap来指明具体编码。artpmap行把负载类型编号和具体编码格式对应起来格式是artpmap:负载类型 编码名称/时钟频率[/声道数]。比如artpmap:96 PS/90000表示负载类型96对应PS流时钟频率90000。afmtp行则用来传递编码相关的附加参数比如afmtp:96 profile-level-id...。asendonly、arecvonly、asendrecv、ainactive这几个属性描述媒体方向在点播、回放、对讲等不同场景下取值不同。方向搞反了就会出现“能发不能收”或者“能收不能发”的问题。2.4 时间描述与连接信息t行描述会话的起止时间格式是t开始时间 结束时间单位是秒使用的是NTP时间戳。如果开始和结束都是0表示会话不受时间限制。c行描述连接信息格式是c网络类型 地址类型 连接地址网络类型一般是IN表示Internet地址类型IP4或IP6连接地址就是具体的IP。在监控场景里c行的地址经常成为问题焦点。设备在NAT后面时它填的可能是自己的内网地址平台侧拿到这个地址后根本连不上。这时候就需要平台侧根据信令的实际来源地址做地址修正或者设备侧主动填写公网映射地址。这个处理逻辑在GB28181里也有相应的约定。3. GB28181对SDP的具体规定哪些字段被改了、哪些被锁死了3.1 GB28181为什么需要约束SDP通用SDP的灵活性很高同一个媒体流可以用很多种方式描述字段可选可省格式也有多种写法。这种灵活性在通用场景下是优点但在监控联网场景下就成了麻烦。监控设备种类繁多平台也五花八门如果大家都按自己的理解写SDP互操作就会变成灾难。所以GB28181在采用SDP时做了一件很自然的事把很多字段的取值、格式、必选性都固定下来形成一个“受约束的SDP子集”。这个约束体现在几个层面。第一某些字段从可选变成必选比如c在GB28181的媒体描述里基本是必须的。第二某些字段的取值被限定比如m行的传输协议基本固定为RTP/AVP。第三增加了监控场景特有的属性行比如asetup、aconnection、assrc等用来描述TCP被动/主动连接、SSRC标识等。第四对编码格式的负载类型做了约定比如PS流常用96音频常用8PCMA或0PCMU。理解这个约束的出发点很重要GB28181不是为了限制而限制而是为了让不同厂商的设备能在最小协商成本下互通。你按这个约束来写对端大概率能认你不按这个来对端可能直接拒绝或者解析异常。3.2 会话级字段的强制要求在GB28181的SDP里会话级字段有几个是必须出现的。v0固定不变表示SDP版本。o字段的格式被明确要求用户名、会话ID、会话版本、网络类型、地址类型、地址都要有其中会话ID和会话版本在点播、回放等场景下会被平台用来做会话管理。s字段一般填Play、Playback、Download等表示会话类型虽然通用SDP里s可以是任意字符串但GB28181场景下通常有约定俗成的取值。t行在点播场景下一般写t0 0表示不受时间限制在回放场景下可能会带上实际的起止时间。c行在会话级可以出现也可以不出现如果出现媒体级可以继承。但实际对接中为了减少歧义很多实现会在媒体级也写上c。这里有个实操经验o里的会话版本如果每次请求都不变某些平台会认为是重复请求而丢弃。我在一次对接中就遇到过设备端每次点播都用同一个会话版本号平台侧收到第二个请求后直接返回错误。后来把会话版本改成每次递增问题就解决了。这个点通用SDP文档里不会强调但GB28181的实际实现里很常见。3.3 媒体级字段的约束与扩展媒体级是GB28181约束最集中的地方。m行的媒体类型一般是video或audio视频点播时通常是video对讲时可能是audio。端口号由设备或平台指定传输协议固定为RTP/AVP。格式列表里视频常用96表示PS流音频常用8表示PCMA也有用0表示PCMU的。artpmap行在GB28181里是必须的用来把负载类型和编码名称对应起来。视频的artpmap:96 PS/90000几乎是标配音频的artpmap:8 PCMA/8000也很常见。afmtp行在视频里可能用来传递分辨率、帧率等参数但GB28181对这块的约束相对宽松不同厂商实现有差异。asetup和aconnection是GB28181里比较有特色的属性。asetup用来协商TCP连接的角色取值有active、passive、actpass分别表示主动发起连接、被动等待连接、两者皆可。aconnection取值new表示新建连接。这两个属性在TCP媒体传输场景下非常关键UDP场景下一般不需要。assrc行用来标识RTP流的同步源格式是assrc:SSRC值。在GB28181里SSRC经常被用来做流的唯一标识平台侧可能会根据SSRC来区分不同的媒体流。如果SSRC冲突或者格式不对可能导致流被错误地关联或丢弃。3.4 编码格式与负载类型的对应关系GB28181场景下常见的编码格式和负载类型对应关系我整理了一个表方便对照媒体类型编码格式负载类型时钟频率典型rtpmap写法视频PS9690000artpmap:96 PS/90000视频H2649890000artpmap:98 H264/90000音频PCMA88000artpmap:8 PCMA/8000音频PCMU08000artpmap:0 PCMU/8000音频G72298000artpmap:9 G722/8000需要说明的是PS流在GB28181里是视频封装的主流方式因为PS流可以同时承载视频和音频适合监控场景。H264裸流也有使用但相对少一些。音频方面PCMAG711A在国内监控设备里非常普遍PCMUG711U也有但少一些。负载类型96到127是动态负载类型具体对应什么编码需要靠artpmap来指明。0到95是静态负载类型有固定的对应关系比如0对应PCMU8对应PCMA。GB28181里视频用96这种动态类型音频用8这种静态类型是比较常见的组合。4. 实际对接中的SDP交互流程与关键环节4.1 点播场景下的SDP交换过程点播是GB28181里最典型的SDP交互场景。整个过程大致是这样的平台侧向设备侧发送INVITE请求请求里携带平台侧的SDP描述平台希望接收媒体的地址、端口、编码格式等信息。设备侧收到INVITE后返回200 OK响应响应里携带设备侧的SDP描述设备实际发送媒体的地址、端口、编码格式。平台侧收到200 OK后发送ACK确认然后媒体流开始传输。这个过程中平台侧的SDP里c行填的是平台接收媒体的地址m行的端口是平台接收媒体的端口。设备侧的SDP里c行填的是设备发送媒体的地址m行的端口是设备发送媒体的端口。如果设备在NAT后面设备侧SDP里的地址可能是内网地址平台侧需要根据信令的实际来源地址做修正或者设备侧主动填写映射后的公网地址。这里有个细节平台侧在INVITE里携带的SDParecvonly表示平台只接收不发送设备侧返回的SDP里asendonly表示设备只发送不接收。方向属性必须匹配否则媒体协商会失败。我见过有实现把方向写反了结果平台一直等不到流排查了半天才发现是SDP方向问题。4.2 回放与下载场景的SDP差异回放场景和点播场景的SDP结构基本一致但有几个差异点。回放时s字段一般填Playbackt行可能会带上实际的回放起止时间。回放还涉及倍速、暂停、拖动等控制这些控制信令一般通过INFO消息传递不直接体现在SDP里。下载场景和回放的差异更大一些。下载通常使用TCP传输SDP里会出现asetup和aconnection属性来协商TCP连接。下载的m行传输协议可能还是RTP/AVP但实际承载在TCP上。下载场景对SDP的约束更严格因为TCP连接的建立需要双方明确角色。回放和下载场景下o字段的会话ID和会话版本管理更重要因为一个回放会话可能持续较长时间期间可能有暂停、恢复等操作会话版本需要正确递增以区分不同的请求。4.3 对讲场景的SDP特点对讲场景的SDP和点播有明显不同。对讲是双向的平台侧既要发送音频也要接收音频所以SDP里会出现asendrecv。对讲的媒体类型通常是audio编码格式常用PCMA或PCMU。对讲场景下平台侧和设备侧都需要在SDP里描述自己的接收地址和端口。对讲场景还有一个特点是平台侧可能需要先向设备侧发送INVITE建立对讲会话设备侧返回200 OK后双方开始双向传输音频。对讲会话的SDP里m行的端口是双方各自接收音频的端口c行是各自接收音频的地址。如果一方在NAT后面地址修正同样需要考虑。对讲场景的SDP协商失败常见原因包括编码格式不匹配、方向属性错误、端口不可达等。排查时可以先确认双方SDP里的编码格式是否一致再确认方向属性是否匹配最后检查网络连通性。4.4 地址修正与NAT穿透的处理思路NAT穿透是GB28181对接里绕不开的问题。设备在NAT后面时它自己知道的地址是内网地址但平台侧需要的是公网地址。处理思路一般有两种一种是在SIP信令层做地址修正平台侧根据收到信令的源地址替换SDP里的c地址另一种是在设备侧配置映射后的公网地址让设备直接在SDP里填公网地址。第一种思路更通用因为平台侧通常能看到信令的实际来源地址。具体做法是平台侧收到设备侧的200 OK后解析SDP把c行里的地址替换成信令的源地址如果m行的端口也需要修正一并替换。这种修正需要在信令层做媒体层感知不到。第二种思路需要设备侧支持配置公网地址配置对了就简单配置错了就麻烦。实际项目中第一种思路更常见也更可控。但要注意如果信令经过了多层NAT源地址可能也不是最终的公网地址这时候可能需要结合其他信息来判断。5. 常见问题与排查技巧实录5.1 SDP解析失败与格式错误SDP解析失败是最常见的问题之一表现是平台侧或设备侧直接返回错误会话建立不起来。常见原因包括行格式不对比如缺少或者类型字母不对必选字段缺失比如没有v或m字段顺序错误比如m出现在v之前编码格式不对比如artpmap的时钟频率写错。排查这类问题第一步是抓包看原始SDP文本确认格式是否符合规范。第二步是逐行对照必选字段确认没有遗漏。第三步是检查字段取值比如c的地址类型是IP4还是IP6m的传输协议是不是RTP/AVP。第四步是用SDP解析工具或者自己写个简单的解析脚本把SDP解析一遍看哪一行报错。我个人的经验是SDP解析失败里行格式错误占了一半以上尤其是artpmap行经常有人把时钟频率写错或者漏写。还有一个容易忽略的点是行尾的换行符SDP规范要求每行以CRLF结尾有些实现只用了LF某些严格的解析器会报错。5.2 媒体流不通的排查路径媒体流不通的表现是信令通了但看不到画面或者听不到声音。排查路径可以按以下顺序来先确认SDP里的地址和端口是否可达用ping或telnet测试再确认媒体流是否真的发出来了在设备侧或平台侧抓包看有没有RTP包然后确认RTP包的负载类型和SDP里描述的是否一致最后确认编码格式是否匹配比如设备发的是PS流平台按H264解析那肯定解不出来。地址不可达是最常见的原因尤其是NAT场景下。端口不可达可能是防火墙拦截也可能是端口写错了。RTP包没有发出来可能是设备侧媒体发送模块有问题也可能是SDP协商的方向属性不对设备以为不需要发送。负载类型不一致可能是SDP里写的负载类型和实际RTP包里的负载类型对不上。编码格式不匹配可能是双方对同一个负载类型的理解不同。排查时建议按“先网络后媒体、先信令后数据”的顺序来不要一上来就怀疑编码问题很多时候就是地址或端口的小问题。5.3 编码协商失败的典型表现编码协商失败的表现是信令通了媒体流也发了但平台侧解码失败或者花屏。常见原因包括SDP里声明的编码格式和实际发送的不一致afmtp参数缺失或错误负载类型冲突比如两个媒体描述用了同一个负载类型但对应不同编码。排查这类问题可以先对比双方SDP里的artpmap行确认编码名称、时钟频率是否一致。再检查afmtp行看是否有必要的参数缺失。然后抓RTP包看实际负载类型和SDP里声明的是否一致。最后用解码工具试解确认编码格式是否真的匹配。我遇到过一次比较隐蔽的问题设备侧SDP里写的是PS流负载类型96但实际RTP包里负载类型是98对应H264。平台侧按PS流解析自然解不出来。后来发现是设备侧固件的一个bugSDP生成和RTP打包用了不同的配置。这种问题只能靠抓包对比来发现。5.4 常见问题速查表问题现象可能原因排查方法处理建议SDP解析失败行格式错误、必选字段缺失抓包看原始SDP逐行对照规范修正格式补齐必选字段媒体流不通地址不可达、端口不可达ping/telnet测试抓包看RTP修正地址端口检查防火墙媒体流单向方向属性错误检查asendonly/recvonly修正方向属性解码失败编码格式不匹配对比rtpmap抓包看负载类型统一编码格式花屏fmtp参数错误检查fmtp行对比参数修正fmtp参数会话重复被拒会话版本未递增检查o行的会话版本每次请求递增版本号TCP连接失败setup属性不匹配检查asetup取值确保一方active一方passiveSSRC冲突SSRC重复或格式错误检查assrc行确保SSRC唯一且格式正确5.5 几个容易踩的坑与实操心得第一个坑是o行的会话版本。很多实现图省事每次都用同一个版本号结果平台侧认为是重复请求直接丢弃。我的做法是每次请求都把会话版本加一确保每次都是新的。第二个坑是c行的地址继承。会话级写了c媒体级没写媒体级继承会话级的地址。但如果媒体级写了c会话级的就被覆盖。有些实现没搞清楚这个继承关系导致地址用错。我的建议是媒体级也显式写上c减少歧义。第三个坑是artpmap的时钟频率。视频一般是90000音频一般是8000写错了解析器可能不报错但协商会失败。我见过把视频时钟频率写成8000的结果平台侧按8000解析时间戳全乱。第四个坑是TCP场景下的asetup。一方写active另一方必须写passive如果两边都写active或者都写passiveTCP连接建不起来。这个在UDP场景下不是问题但TCP场景下必须注意。第五个坑是SDP文本的换行符。规范要求CRLF有些实现只用了LF严格的解析器会报错。如果遇到莫名其妙的解析失败可以检查一下换行符。6. 从SDP到GB28181一套可复用的对接检查清单6.1 信令层检查要点信令层是SDP交互的载体信令层有问题SDP再正确也没用。检查要点包括SIP消息的格式是否正确请求方法和响应码是否匹配INVITE、ACK、BYE等消息的交互流程是否完整SIP头域里的From、To、Call-ID、CSeq等字段是否正确信令的传输协议是UDP还是TCP是否和SDP里的媒体传输协议匹配。信令层还有一个容易忽略的点是超时和重传。INVITE发出后如果没有收到响应可能会重传重传的INVITE里SDP的会话版本应该递增否则对端可能认为是重复请求。ACK的发送时机也很关键必须在收到200 OK后发送否则会话建立不起来。6.2 媒体层检查要点媒体层检查的核心是地址、端口、编码、方向四个要素。地址要确认是公网可达的地址端口要确认没有被防火墙拦截编码要确认双方一致方向要确认匹配。这四个要素里任何一个出问题媒体流都通不了。媒体层还有一个检查点是RTP包的SSRC和负载类型。SSRC要唯一负载类型要和SDP里声明的一致。如果平台侧根据SSRC做流关联SSRC冲突会导致流被错误关联。负载类型不一致会导致解码失败。6.3 编码与封装检查要点编码与封装检查包括视频编码是PS还是H264裸流音频编码是PCMA还是PCMU封装格式是否符合GB28181要求。PS流的封装有特定的格式要求比如PS头、PES头、系统头等封装不对可能导致平台侧解不出来。afmtp行的参数也要检查比如H264的profile-level-id、sprop-parameter-sets等这些参数影响解码器的初始化。参数缺失或错误可能导致解码失败或花屏。6.4 对接前的自检清单在实际对接前建议按以下清单自检一遍SDP格式是否符合规范每行是否以CRLF结尾必选字段是否齐全v、o、s、t、m是否都有c行的地址是否可达端口是否开放artpmap行的编码名称、时钟频率是否正确afmtp行的参数是否完整方向属性是否匹配sendonly对recvonlysendrecv对sendrecvo行的会话版本是否每次递增TCP场景下asetup是否一方active一方passiveSSRC是否唯一且格式正确媒体传输协议是否和信令传输协议匹配这个清单看起来简单但实际对接中能全部满足的并不多。我个人的习惯是每次对接前都过一遍这个清单能省下大量排查时间。6.5 一个简化的SDP示例与逐行说明下面是一个GB28181点播场景下比较典型的SDP示例我逐行说明一下v0 o34020000001320000001 1234567890 1234567890 IN IP4 192.168.1.100 sPlay cIN IP4 192.168.1.100 t0 0 mvideo 6000 RTP/AVP 96 artpmap:96 PS/90000 asendonly assrc:1234567890v0表示SDP版本为0固定写法。o行里用户名是设备编号会话ID和会话版本都是1234567890网络类型IN地址类型IP4地址是192.168.1.100。sPlay表示这是一个点播会话。cIN IP4 192.168.1.100表示连接地址是192.168.1.100。t0 0表示会话不受时间限制。mvideo 6000 RTP/AVP 96表示视频媒体端口6000传输协议RTP/AVP负载类型96。artpmap:96 PS/90000表示负载类型96对应PS流时钟频率90000。asendonly表示只发送不接收。assrc:1234567890表示SSRC为1234567890。这个示例里c行的地址是内网地址实际对接时需要根据NAT情况做修正。o行的会话版本在实际使用中应该每次递增。assrc的值应该保证唯一。这些细节在实际对接中都需要注意。7. 写在最后一些个人体会SDP这个东西刚接触的时候觉得很简单不就是几行文本吗。但真正在GB28181场景下对接多了就会发现细节非常多每一个字段背后都可能藏着坑。我自己的体会是不要试图一次性把所有字段都记住而是理解每个字段的作用和约束遇到问题时知道去哪里查、怎么排查。GB28181对SDP的约束本质上是为了降低互操作成本。你按约束来写对端大概率能认你不按约束来对端可能直接拒绝。所以对接前先把约束搞清楚比事后排查要省事得多。还有一个体会是抓包是最有效的排查手段。SDP的问题信令的问题媒体的问题抓包一看就清楚。不要靠猜要靠数据。我见过太多人靠猜来排查问题结果绕了一大圈才发现是最简单的原因。最后分享一个小技巧如果遇到SDP解析失败但看不出哪里有问题可以把SDP文本复制到一个十六进制编辑器里看看有没有不可见字符或者换行符问题。这个技巧帮我解决过好几次莫名其妙的解析失败。