1. 从一次对接失败说起:GB28181到底在解决什么问题
我第一次接触GB28181是在一个园区安防改造项目上。甲方手里有海康、大华、宇视三个品牌的摄像机,还有一套老旧的监控客户端,要求把所有视频统一汇到一个平台上,还要能上墙、能录像、能语音喊话。当时我第一反应是各品牌SDK挨个对接,结果光是海康一家的SDK就把我折腾了两天,换个大华又要重写一遍。后来一位做安防的老哥点了我一句:你为什么不走国标?这才有了后面这段GB28181的学习和踩坑经历。
GB28181的全称是《公共安全视频监控联网系统信息传输、交换、控制技术要求》,它本质上是一套信令加媒体的联网规范。信令部分用SIP,媒体部分用RTP/RTCP,设备控制用MANSCDP,回放控制用MANSRTSP。你可以把它理解成安防界的普通话:不管你是海康、大华还是宇视,只要都说普通话,平台就能听懂。它解决的核心问题就是异构设备互联互通,让不同厂商、不同型号的摄像机、NVR、平台之间能互相注册、点播、控制、回放。
这套东西适合谁学?如果你是做安防平台开发的、做流媒体服务的、做物联网视频接入的,或者你手里有一堆摄像机想自己搭个平台,那GB28181是绕不过去的。哪怕你只是用Python写个小服务把家里的摄像头接进来,理解这套协议也能让你少走很多弯路。下面我按自己实际学习和落地的顺序,把GB28181拆开讲一遍,尽量说人话,把那些文档里不写、但实际会卡住你的地方都点出来。
2. 协议整体架构与核心角色拆解
2.1 SIP、RTP、MANSCDP、MANSRTSP各自管什么
刚看GB28181文档的时候,最容易懵的就是一堆缩写。我当时的做法是先把它们按职责分堆,分完就清楚多了。
SIP是信令通道,负责“说话”。设备注册、心跳、目录查询、点播请求、云台控制指令,这些都是SIP消息在传。你可以把SIP理解成打电话时的拨号和通话控制,它不传视频画面,只传“我要干什么”。
RTP是媒体通道,负责“传画面”。真正的一帧帧视频、一段段音频,都是打包成RTP包发出去的。RTCP是它的搭档,负责统计丢包、抖动这些质量信息。这里有个新手常踩的坑:SIP和RTP走的是不同的端口,SIP通常是5060,RTP是动态协商的,别以为通了SIP就能看到画面。
MANSCDP是设备控制协议,基于XML,跑在SIP消息体里。比如你查设备目录、查设备状态、发起录像回放,用的就是MANSCDP。它规定了XML的格式和字段,设备按这个格式回你。
MANSRTSP是回放控制协议,也是基于SIP消息体,专门管录像回放的暂停、快进、拖拽。实时点播用不到它,但你要做录像回放功能,就必须把它搞明白。
| 协议 | 职责 | 承载方式 | 典型场景 |
|---|---|---|---|
| SIP | 信令控制 | UDP/TCP 5060 | 注册、心跳、点播、云台 |
| RTP/RTCP | 媒体传输 | UDP 动态端口 | 视频流、音频流 |
| MANSCDP | 设备控制 | SIP消息体XML | 目录查询、状态查询、回放 |
| MANSRTSP | 回放控制 | SIP消息体 | 暂停、快进、拖拽 |
2.2 平台、设备、客户端三种角色的关系
GB28181里角色分得很清楚。平台是上级,负责接收注册、下发指令;设备是下级,摄像机、NVR、编码器都算;客户端是操作端,可以理解为看画面、发指令的那一端。实际部署里,平台和设备是必须的,客户端可以是平台的一部分,也可以是独立的一套。
我见过很多人把“平台”和“客户端”混在一起说,结果对接的时候双方理解不一致,一个以为你要注册,一个以为你要点播,白白浪费半天。所以对接前一定要先确认:谁是平台,谁是设备,谁主动发起注册。GB28181默认是设备主动向平台注册,平台被动接收。如果双方都等着对方注册,那就永远通不了。
2.3 一次完整点播的信令流程
我把一次实时点播的流程简化成下面几步,实际抓包也是这个顺序:
- 设备向平台注册,平台回200 OK。
- 平台向设备发INVITE,消息体里带SDP,告诉设备我要收流的IP和端口。
- 设备回200 OK,消息体里带自己的SDP,告诉平台我往哪个IP和端口发流。
- 平台回ACK,确认。
- 设备开始往协商好的端口发RTP流。
- 平台发BYE结束会话,设备停止发流。
这里面第2步和第3步的SDP协商是重点。SDP里会写媒体类型、编码格式、端口、SSRC。SSRC是流的唯一标识,平台靠它区分不同设备的流。我遇到过设备回的SSRC和平台预期不一致,导致平台收流后不知道是谁的,画面出不来。所以对接时一定要把SSRC对齐。
3. 核心细节解析与实操要点
3.1 注册与心跳:别小看这两步
注册是设备上线第一步。设备发REGISTER,平台回401要求鉴权,设备带鉴权信息再发REGISTER,平台回200 OK。鉴权用的是MD5,用户名、密码、realm、nonce这些字段都要对。我踩过的坑是:密码里带特殊字符,设备端和平台端对特殊字符的处理不一致,导致鉴权一直失败。后来把密码改成纯字母数字就通了。所以对接初期,密码尽量简单,通了再改复杂。
心跳是设备定期告诉平台“我还活着”。默认是60秒一次,用MESSAGE消息发Keepalive。平台如果连续几个心跳没收到,就认为设备离线。这里有个细节:心跳超时时间要设得比心跳间隔大,比如心跳60秒,超时设180秒,否则网络稍微抖一下设备就掉线了。我见过有人把超时设成60秒,结果设备频繁上下线,排查半天才发现是超时太短。
3.2 目录查询:设备到底有哪些通道
目录查询用MANSCDP,平台发Catalog查询,设备回Catalog响应,里面列出所有通道。每个通道有设备ID、名称、状态、类型。这里要注意:一个NVR可能带多个通道,每个通道有自己的ID,点播时要按通道ID点,不能按NVR的ID点。我一开始没注意,点播NVR的ID,结果设备回错误,后来改成通道ID才通。
目录查询还分全量和增量。全量是查所有,增量是查变化的。实际对接时,平台一般先全量查一次,之后定期增量查。增量查询的SN字段要递增,设备靠SN区分请求。如果SN不递增,设备可能不响应。
3.3 实时点播:SDP协商里的门道
实时点播是核心功能,也是最容易出问题的地方。INVITE消息里的SDP,平台要写清楚自己收流的IP、端口、媒体类型。设备回的SDP里,要写清楚它发流的IP、端口、编码格式、SSRC。
我总结几个关键点:
- 收流IP:平台如果有多网卡,一定要写对IP,否则设备往错误的IP发流,平台收不到。
- 端口:平台要提前开好端口,别等设备发流了才开,那样流就丢了。
- 编码格式:平台要声明自己支持哪些编码,设备选一个它支持的。如果平台只声明H265,设备只支持H264,那就协商失败。
- SSRC:平台可以指定SSRC,也可以让设备自己生成。如果平台指定,设备要按平台的来;如果设备自己生成,平台要能接受任意SSRC。
提示:对接初期,建议平台把收流端口范围开大一点,比如从30000到40000,避免端口冲突。同时把防火墙对应端口放行,否则SIP通了RTP不通,画面就是黑的。
3.4 语音对讲:容易被忽略的双向流
语音对讲是很多项目的刚需,但GB28181文档里写得比较简略。它本质上是平台向设备发一个INVITE,SDP里声明是音频,设备回SDP,然后平台往设备发RTP音频流,设备播放。同时设备也可以往平台发音频流,实现双向对讲。
我踩过的坑是:音频编码格式不匹配。平台用G711A,设备只支持G711U,结果对讲没声音。后来统一成G711A才通。另外,对讲对延迟敏感,RTP包要小,发送间隔要短,否则听起来一顿一顿的。
3.5 录像回放:MANSRTSP控制流
录像回放比实时点播多了一步:平台先发INVITE,SDP里声明是回放,设备回SDP,然后平台发MANSRTSP指令控制播放。MANSRTSP指令包括PLAY、PAUSE、TEARDOWN,还有快进、慢放、拖拽。
这里的关键是时间戳。回放请求里要带开始时间和结束时间,设备按这个时间段发流。如果时间格式不对,设备可能不响应。GB28181用的是ISO8601格式,比如2024-01-01T00:00:00。我见过有人用Unix时间戳,设备直接忽略。
4. 实操过程与核心环节实现
4.1 环境准备与工具选型
我搭测试环境用的是一台Linux服务器跑平台,一台海康摄像机做设备,中间用交换机连。工具方面,抓包用Wireshark,SIP消息看得很清楚;测试SIP用SIPp,可以模拟设备注册和点播;流媒体服务用ZLMediaKit,它自带GB28181支持,省了很多事。
如果你不想用现成的,想自己写,Python可以用pjsua或者aiosip做SIP,用aiortc或者直接socket收RTP。但说实话,自己写SIP栈工作量不小,建议先用现成库跑通,再考虑自己实现。
4.2 平台侧收流端口规划
平台侧要规划好端口。SIP用5060,RTP用30000到40000。每个点播会话分配一个RTP端口,会话结束后回收。我一般用端口池管理,分配时从池里取,释放时还回去。这样避免端口冲突,也方便防火墙配置。
# 简单的端口池实现 import threading class PortPool: def __init__(self, start, end): self.ports = list(range(start, end + 1)) self.lock = threading.Lock() def acquire(self): with self.lock: if self.ports: return self.ports.pop(0) return None def release(self, port): with self.lock: if port not in self.ports: self.ports.append(port) self.ports.sort()这个端口池很简单,但够用。实际生产环境要考虑端口回收超时,避免会话异常结束后端口不释放。
4.3 设备侧注册配置
海康摄像机的GB28181配置在“网络-高级配置-平台接入”里。要填平台IP、端口、设备ID、密码。设备ID是20位,前8位是行政区划,中间8位是行业编码,后4位是设备序号。这个ID要和平台侧配置一致,否则注册失败。
配置完保存,设备会主动向平台注册。如果平台没收到注册,先检查网络通不通,再检查端口对不对,最后检查ID和密码。我一般用Wireshark抓包,看设备有没有发REGISTER,平台有没有回401,设备有没有再发带鉴权的REGISTER。这三步一看就知道卡在哪。
4.4 点播流程的代码实现
下面是一个简化的点播流程,用Python模拟平台向设备发INVITE。实际代码要复杂得多,但核心逻辑就这些。
import socket def send_invite(device_ip, device_port, platform_ip, rtp_port, ssrc): invite = f"""INVITE sip:{device_id}@{device_ip}:{device_port} SIP/2.0 Via: SIP/2.0/UDP {platform_ip}:5060 From: <sip:platform@{platform_ip}>;tag=12345 To: <sip:{device_id}@{device_ip}> Call-ID: 123456789 CSeq: 1 INVITE Content-Type: application/sdp v=0 o={platform_ip} 0 0 IN IP4 {platform_ip} s=Play c=IN IP4 {platform_ip} t=0 0 m=video {rtp_port} RTP/AVP 96 a=rtpmap:96 PS/90000 a=ssrc:{ssrc} """ sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.sendto(invite.encode(), (device_ip, device_port))这段代码只是示意,实际要处理鉴权、重传、超时。但你可以看到,INVITE里带了SDP,声明了收流IP、端口、编码、SSRC。设备收到后回200 OK,带它自己的SDP,然后平台回ACK,设备开始发流。
4.5 收流与解码
收流用socket监听RTP端口,收到包后解析RTP头,取出payload,按编码格式解码。如果是PS流,要先解PS,再解H264/H265。ZLMediaKit这些流媒体服务已经帮你做好了,你只需要把流推给它,它负责解码和分发。
我一般用FFmpeg收流,命令很简单:
ffmpeg -i rtp://192.168.1.100:30000 -c copy output.mp4但要注意,FFmpeg收RTP需要SDP文件,或者你直接指定编码格式。如果流是PS封装的,FFmpeg也能处理,但有时候需要加-f mpegts参数。
5. 常见问题与排查技巧实录
5.1 注册失败:从抓包开始
注册失败是最常见的问题。我的排查顺序是:先ping设备,确认网络通;再telnet设备5060端口,确认SIP端口开;然后Wireshark抓包,看REGISTER有没有发出来,平台有没有回401,设备有没有再发REGISTER。如果设备发了REGISTER但平台没回,可能是平台没监听5060,或者防火墙拦了。如果平台回了401但设备没再发,可能是设备鉴权配置不对。
注意:有些设备默认用TCP发SIP,平台如果只监听UDP,就收不到。对接前确认双方传输协议一致。
5.2 点播无画面:SIP通了RTP不通
SIP通了但画面黑,说明信令没问题,媒体有问题。排查步骤:先看平台有没有收到RTP包,用tcpdump抓包;如果有包但画面黑,可能是编码不匹配,检查SDP里的编码格式;如果没包,可能是设备往错误的IP或端口发流,检查INVITE里的收流IP和端口。
我遇到过一种情况:平台有多网卡,INVITE里写的收流IP是A网卡,但设备只能通B网卡,结果设备往A网卡发流,平台收不到。后来把收流IP改成B网卡就通了。所以多网卡环境一定要确认路由。
5.3 语音对讲没声音:编码和方向都要查
语音对讲没声音,先查编码格式,平台和设备要一致。再查方向,平台要往设备发RTP,设备也要往平台发RTP,如果只配了一个方向,就是单向对讲。最后查音量,有些设备默认音量是0,要手动调。
5.4 回放卡顿:时间戳和关键帧
回放卡顿,先查时间戳,设备发流的时间戳要连续,如果跳变,播放器会卡。再查关键帧,如果关键帧间隔太长,拖拽后会等很久才出画面。我一般把关键帧间隔设成1秒,拖拽响应快。
5.5 常见问题速查表
| 问题 | 可能原因 | 排查方法 |
|---|---|---|
| 注册失败 | 网络不通、端口不对、鉴权失败 | ping、telnet、抓包 |
| 点播无画面 | 收流IP错、端口未开、编码不匹配 | tcpdump、检查SDP |
| 语音没声音 | 编码不一致、方向不对、音量为0 | 检查SDP、调音量 |
| 回放卡顿 | 时间戳跳变、关键帧间隔长 | 检查时间戳、调关键帧 |
| 设备频繁离线 | 心跳超时太短 | 调大超时时间 |
5.6 几个独家避坑技巧
第一,对接初期用最简单的配置。密码用纯数字,编码用H264,传输用UDP,端口用默认。通了再改复杂,这样出问题容易定位。
第二,抓包是王道。SIP和RTP的问题,抓包一看就清楚。Wireshark有SIP解析功能,能直接看消息内容。RTP包也能看序列号和时间戳,丢包和乱序一目了然。
第三,SSRC要对齐。平台指定SSRC时,设备要按平台的来;设备自己生成时,平台要能接受。我见过平台只接受指定SSRC,设备自己生成,结果平台收流后丢弃,画面出不来。
第四,端口范围要开够。一个平台可能同时点播几十路,端口不够就会失败。我一般开10000个端口,从30000到40000,基本够用。
第五,时间同步。设备和平台的时间要同步,否则回放时间戳对不上。我一般用NTP同步,误差控制在1秒内。
6. 从能跑到好用:性能与扩展的几点经验
跑通之后,下一步就是让它稳定、好用。我总结几个实际项目里验证过的点。
并发点播。一个平台同时点播多路时,SIP信令和RTP收流都要能扛住。SIP可以用多线程或异步处理,RTP收流每个会话一个线程或协程。我试过用Python的asyncio收流,单机跑50路没问题,再往上就要考虑用C++或者Go重写收流部分。
流媒体分发。平台收到流后,要分发给多个客户端看。这时候可以用ZLMediaKit或者SRS做流媒体服务,平台把流推给它们,它们负责转协议、分发。这样平台只负责信令和收流,分发交给专业服务,架构更清晰。
录像存储。录像可以存成MP4或者TS文件,按时间和设备ID命名。我一般用TS,因为TS对断电流友好,MP4断电流文件可能损坏。存储路径按日期分目录,方便查找。
级联。GB28181支持平台级联,下级平台向上级平台注册,上级平台可以点播下级平台的设备。级联的信令和点播类似,但多了一层转发。我做过三级级联,延迟会增加,但功能没问题。级联时要注意ID规划,避免冲突。
安全。GB28181默认用MD5鉴权,安全性一般。实际项目里,我一般把SIP和RTP限制在内网,或者用专线,避免暴露在公网。如果必须公网,加个防火墙规则,只允许特定IP访问。
7. 我个人的学习路径和建议
回头看,我学GB28181的过程大概是:先看文档,把SIP、RTP、MANSCDP、MANSRTSP分清楚;然后搭环境,用现成平台和设备跑通注册和点播;接着抓包,把每一步的信令和媒体都看一遍;最后自己写代码,实现一个简单的平台。
如果你刚开始学,我建议你按这个顺序来:先跑通,再抓包,再改代码。别一上来就自己写SIP栈,那样容易卡在细节里。用ZLMediaKit或者SIPp这些现成工具,先把流程跑通,有了直观感受,再深入细节。
另外,多设备对接是常态。我手里常备海康、大华、宇视各一台,每次改代码都拿它们测一遍。不同厂商对协议的理解有差异,比如有的设备回SDP时SSRC是0,有的设备心跳间隔不是60秒。这些差异文档里不写,只能靠实测。
最后分享一个小技巧:如果你在对接时卡住了,先别急着改代码,把SIP和RTP抓包发给对方,让对方也抓一份,两边一对,问题基本就定位了。我靠这招解决过很多次扯皮,比来回猜效率高多了。