Gb28181解析
国标GB28181(以下简称国标或者gb)文档分析的文档我去年其实写过一版,但是由于原来的电脑炸了,文档还没来得及传上去就丢了(痛苦),所以还要重写一版做笔记。
国标目前有两个版本,2011和2016,以下以16版为准。16版本相比11版本,主要是增加了TCP的支持,11版是全部基于UDP传输的。
协议结构
上图是直接从文档中截取的通信协议结构图。国标基于TCP/IP网络,主要分为两个传输通道,即一般会话通道和流媒体传输通道。前者基于SIP协议(图中MANSCDP
和MANSRTSP
是国标自行扩展的协议,仍然是基于SIP协议的),传输载体是SDP格式字符串;后者基于RTP/RTCP协议,传输载体主要是PS视频流和AAC等格式的音频数据。
SIP协议
SIP协议初衷就是用于音视频通话,VOIP等技术大量用到该协议。
RFC3261规定了SIP协议的基础内容,包括Register
, Invite
等一系列注册和通信机制。
SIP协议某些设计非常类似HTTP1.1,比如消息格式,method、header的设计等。但是SIP协议远比HTTP要复杂的多:
- SIP协议的传输层是可选的,可以基于TCP/UDP/Websocket等进行通信;在UDP这种不可靠网络上的通信,需要考虑的问题要复杂很多;
- 相比于HTTP的请求-应答模型,SIP是基于事务进行通信的,整个流程比较复杂;
- 相比于HTTP的option/get/post/put/patch/delete/head等有限几个方法,SIP的方法要多得多。除了rfc3261规定的,还有一系列扩展协议(如info, publish, notify, subscribe等);
- 对于HTTP而言,代理基本是透明的,只负责转发,相当于SIP的无状态代理;SIP的有状态代理工作机制比较复杂;
基础概念
- UAC: 在事务中充当请求客户端的一方;
- UAS:在事务中充当请求服务端的一方;
- UA:同时充当UAC和UAS的逻辑实体;
在实际运行中,一般节点都既是客户端又是服务器。谈到UAC和UAS时指的往往是在某个事务中的角色,比如INVITE时,呼叫方是UAC,接收方是UAS;但是在BYE的时候,就可能反过来了。
Call
一次呼叫由发起方和多个接收方组成。唯一标识是Call-ID
,Call由多个Dialog组成。
Call强调的是终端用户的对话,实际请求中,Call会经过很多代理服务器。
对话(Dialog)
主叫方A呼叫被叫方B:
- 步骤1:
主叫方A
发送INVITE
请求到代理服务器
; - 步骤2:
代理服务器
发送100 Trying 响应主叫方A
; - 步骤3~6:
代理服务器
搜索被叫方B
的地址,获取地址后转发INVITE请求; - 步骤7~9:
被叫方B
生成的180 振铃响应,返回给主叫方A
; - 步骤10~12:
被叫方B
生成的200 OK响应,返回给主叫方A
; - 步骤13~17:
主叫方A
收到被叫方B
200 OK响应后,向被叫方B
发送一个ACK,会话建立; - 步骤18~20:会话结束后,任何参与者(A或B)都可以发送一个BYE请求来终止会话;
- 步骤21~23:
主叫方A
发送200 OK响应来确认BYE,会话终止。
以上的整个流程称之为一个Dialog
,其中流媒体传输的部分(媒体通道)称为Session
。RFC3261里面仅定义了invite
可以建立对话,在RFC 3265/6665里面额外添加了Subscribe
方法创建对话的机制。Info
消息可以对对话进行设置,Bye
消息可以结束对话,这些都是对话内消息。ACK属于非对话内非会话消息,直接发出即可,也没有Response。
对话其实比较容易理解,Message请求是单次请求-应答机制(类似HTTP),无法定义持续性行为。订阅就是典型的持续性行为:在取消订阅之前被订阅端需要持续NOTIFY新的事件,这就需要一个机制来定义这次订阅和相关的响应之间的关联关系,这就是对话。
按着rfc3261的规定,当UAC发出INVITE
请求,收到的2xx和1xx(除了100之外)的响应中To
字段包含tag
时,视为对话建立。Call-ID
, From tag
和To tag
,共同构成本次会话的唯一ID,即Dialog-ID
. 一次呼叫中可能存在多个对话,因此对话中的消息,如BYE
, ACK
显然需要明确的Dialog-ID
才能明确操作的对象。
对话外的SIP消息最初发出的时候,发送端(UAC)会在From
后面生成tag,但是To后面没有,消息接收端(UAS)会在回复的时候在To
后面加上tag。对话中的所有消息同时携带Dialog-ID
的所有组成元素,同一个对话内的消息这些元素必须保持不变。
举例来说,对应于国标场景,前端通过HTTP请求一次打开多个IPC的某个时间段的历史流,UAC发送这些请求时可以共用同一个call-ID
,但是每个请求的from-tag
不一致,各相机返回的to-tag
肯定也不一致,这就建立了多个Dialog
。你可以在不结束这次请求的情况下,单独关掉或者暂停某个摄像头的流,而不影响其他摄像头继续播放。一个简单的设计是call-id
使用请求的ReqId
(前端请求时生成这个uuid),from tag
使用设备id,设备端生成to-tag
. 而流本身的标记可以用DialogID
或其hash值。由于浏览器可以直接关掉,所以服务端需要设计流保活机制,当心跳停止后自动关闭Dialog.
对话中的CSeq
头用于保证消息的顺序性,在同一个Dialog中标识及排序事务(transaction)以及区分新的请求与请求的重发。对于ACK而言,CSeq的序号必须与其所对应的request相同。对于CANCEL而言,CSeq的序号也必须与其cancel掉的request相同。其他情况下,Dialog里面每个新的Request,Cseq+1。需要注意的是:在同一个对话中的UAC和UAS分别维护自己的CSeq序号,他们发出请求的CSeq序号是不相关的。
对话外消息
除了Invite/Subscribe
消息建立的对话外,其他消息都是对话外消息。比如Message
, Register
, Options
等。这种消息类似HTTP的单次请求应答机制:
- 步骤1:
User1
发送MESSAGE
请求到代理服务器
; - 步骤2:
代理服务器
转发User1
的MESSAGE请求给USER2
; - 步骤3:
User2
收到User1
的消息后,回复200 OK给代理服务器
; - 步骤7~9:
代理服务器
转发200 OK回复给User1
显然这个流程比较简单。对话外消息的Call-ID
和From-Tag
一般是随机生成的,且即用即抛。
对话外消息的CSeq
值可以随意使用,一般多次发送同一个请求会递增CSeq
。响应的大部分header必须和请求一致(From/Call-ID/CSeq/Via/To),如果请求的To
没有tag,响应需要生成tag,并保证多次响应的tag一致(参考RFC3261 8.2.6节)。这个设计主要是为了请求可能有多次响应的场景,以及后续需要建立对话的需求,但是标准并没有明确的说明如何生成这个To-Tag.
简单总结一下就是UAS如果发现To不带Tag,就要生成一个Tag,并且对于同样的call-id+from-tag,应该始终使用相同的to-tag。不过对于一次性的请求,这个To tag可以即用即抛。考虑视频/语音会议的场景,当一个人呼叫你的微信时,PC端微信和手机端微信都会收到呼叫,此时两者收到的INVITE拥有同样的call-ID和同样的From-Tag,但是PC端和手机端可以生成不同的To-Tag
,这样就建立了多个对话(当然,实际上微信会直接挂掉另外一个终端)。
Transaction
事务=请求(UAC)+最终响应(相邻的UAS),SIP基于事务。所谓相邻就是说transaction存在于相邻的SIP实体,而不是存在于两个UA之间。
一个事务中包含一个请求消息、0个或多个临时响应消息、1个或多个最终响应消息(2xx~6xx)。事务的区分通过Via字段栈顶的Branch的值来确定。对于请求消息,每经过一个有事务状态的Proxy的时候,该Proxy需要为这个事务创建一个服务器端事务和一个客户端事务,并且将自己的URI添加到Via的栈顶,并生成一个Global ID做为Branch的值,以此值来表示一个与之相对应的事务。SIP在事务层面定义了状态机和定时器来实现重传。
事务中的请求是 INVITE(称为 INVITE 事务)时,只有最终响应不是 2xx 响应时这个事务才包括 ACK。如果最终响应是 2xx,那么不应将 ACK 视为事务的一部分。这是因为200的ACK是比较重要的响应,由UAS负责重传,所以可以视为一个单独的事务。
从事务的定义可以看出,事务发生于一个请求和一组应答之间。所以事务是SIP核心层处理的,一般在业务层无须关心,当然如果手动生成响应的话,记得把request的branch copy过来。
消息体格式
类似HTTP,SIP消息分为三个部分:
- 请求行(request-line) or 状态行(status-line)
- 消息头(header)
- 正文(body)
请求行
请求行格式:Method Request-URI SIP-Version CRLF
请求行举例:INVITE sip:bob@zte.com SIP/2.0 /r/n
Method分为:
Method | 方法说明 |
---|---|
REGISTER | 注册联系信息 |
INVITE | 发起会话请求 |
ACK | INVITE 请求的响应的确认 |
CANCEL | 取消请求 |
BYE | 终结会话 |
OPTIONS | 查询服务器能力 |
MESSAGE | RFC3428,上文已有描述 |
状态行
状态行格式: SIP-Version Status-Code Reason-Phrase CRLF
状态行举例:SIP/2.0 200 OK /r/n
状态码的设计也和HTTP非常类似:
状态码 | 含义 |
---|---|
1xx: | 临时响应、表示请求消息正在被处理 |
2xx | 成功响应、表示请求已被成功接收完全理解并接收 |
3xx | 重定向响应、表示需采取进一步完成请求 |
4xx | 客户机错误、表示请求消息中包含语法错误信息或服务器无法完成客户机的请求 |
5xx | 服务器错误、表示服务器无法合法完成请求 |
6xx | 全局故障 、表示任何服务器都无法完成该请求 |
常用的状态码举例:
状态码 | msg | 含义 |
---|---|---|
100 | Trying | 试呼叫 |
180 | Ringing | 振铃 |
181 | Call is Being Forwarded | 呼叫正在前转 |
200 | OK | 成功响应 |
302 | Moved Temporarily | 临时迁移 |
400 | Bad Request | 错误请求 |
401 | Unauthorized | 未授权 |
403 | Forbidden | 禁止 |
404 | Not Found | 用户不存在 |
408 | Request Timeout | 请求超时 |
480 | Temporarily Unavailable | 暂时无人接听 |
486 | Busy Here | 线路忙 |
504 | Server Time-out | 服务器超时 |
600 | Busy Everywhere | 全忙 |
可以说和HTTP的状态码含义非常接近。
中间响应消息1xx的使用则是为了节省网络开销设计的,一旦 UC 收到任何一个中间响应消息,则UC必须停止消息重发定时器,不再从发这个请求消息,反之则直到收到最终响应消息或重发定时器超时。
Header
格式和HTTP的header也保持一致,典型的k:v
结构。常用header包括:
Header | 含义说明 | 举例 |
---|---|---|
Call-ID | 由本地设备(Client)生成,全局唯一,每次呼叫这个值唯一不变。 | Call-ID: asd88asd77a@1.2.3.4 |
From | 表示请求的发起者 | From: sip:user1@domain.com;tag=49583 |
To | 表示请求的接收者 | To: sip:user2@domain.com |
Via | Via头域是被服务器插入request中,用来检查路由环的,并且可以使response根据via找到返回的路 | Via: SIP/2.0/TCP user1pc.domain.com;branch=z9hG4bK776sgdkse |
Max-Forwards | 用于表示这个包最多可以传送多少跳,每经过一跳都会减一,当Max-Forwards==0系统会返回483。默认为70 | Max-Forwards: 70 |
Contact | 包含源的URI信息,用来给响应方直接和源建立连接用 | Contact: sip:192.168.100.1:1111 |
Content-Type | 指明消息体的类型 | Content-Type: text/plain;Content-Type: application/sdp; Content-Type: application/cpim; |
Content-Length | 指明消息体的字节大小 | Content-Length: 18 |
Authorization | 鉴权凭证消息 | Authorization: Digest username=“01062237493”, realm=“192.168.2.89”, qop=auth, algorithm=MD5, uri=“sip:192.168.2.89”, nonce=“e17d377c3d2d9c343e26576a7fd04738481dfc10”, nc=00000001, cnonce=“12660455546344082314666316435946”, response=“f57e47ce03162293b9ced07362ce2b79” |
MANSCDP
在国标附录A里面有描述,主要是一系列对设备的查询、控制等指令,并规定了请求/应答的数据结构。
MANSCDP使用xml进行通信,作为SIP MESSAGE
类型消息的负载,Content-Type为Application/MANSCDP+xml
。
消息大致分为以下几类,名称就是xml的根标签。
类型 | 名称 | 说明 |
---|---|---|
Request | Control | 控制设备指令 |
Request | Query | 查询设备信息指令 |
Request | Notify | 通知,主要用于设备向云端发送告警。 |
Response | Response | 应答 |
基础数据结构,写代码的话这些就是类或者alias:
再下面是定义的请求/应答格式,这里不再详述。
举个例子,设备远程启动,请求格式:
MESSAGE sip:34020000001320000001@3402000000 SIP/2.0
Call-ID: fac5d9915d7b4b64e46054bbbda3f29f@0.0.0.0
CSeq: 1 MESSAGE
From: <sip:34020000002000000001@3402000000>;tag=58726327_53173353_c2be9be1-4121-42af-b4e2-e4896ab42120
To: <sip:34020000001320000001@3402000000>
Max-Forwards: 70
Content-Type: Application/MANSCDP+xml
Route: <sip:34020000001320000001@192.168.10.177:5061;line=9a1ca01668b5778;lr>
Via: SIP/2.0/UDP 192.168.10.177:5060;branch=z9hG4bKc2be9be1-4121-42af-b4e2-e4896ab42120_53173353_18042259617961
Content-Length: 164
<?xml version="1.0"?>
<Control>
<CmdType>DeviceControl</CmdType>
<SN>17298</SN>
<DeviceID>34020000001320000001</DeviceID>
<TeleBoot>Boot</TeleBoot>
</Control>
应答是先给200,然后异步回应:
MESSAGE sip:34020000001320000001@192.168.10.177 SIP/2.0
Via: SIP/2.0/UDP 192.168.10.177:5061;rport=5061;branch=z9hG4bK3569164341;received=192.168.10.177
From: <sip:192.168.10.177:5060>;tag=3889968117
To: <sip:34020000001320000001@192.168.10.177>
Call-ID: 2825372510
CSeq: 20 MESSAGE
Content-Type: Application/MANSCDP+xml
Max-Forwards: 70
User-Agent: eXosip/3.6.0
Content-Length: 162
<?xml version="1.0"?>
<Response>
<CmdType>DeviceControl</CmdType>
<SN>17298</SN>
<DeviceID>34020000001320000001</DeviceID>
<Result>OK</Result>
</Response>
服务端收到响应之后仍然会回复一个200.
MANSRTSP
类似的,国标扩展了SIP协议以支持媒体控制。MANSRTSP采用RTSP协议(RTC2326)规定的格式,所以算是缝合和SIP和RTSP。Method使用INFO
,Content-Type
设为 Application/MANSRTSP
.
示例格式:
message=start-line message header CRLF [message body]
四个部分:起始行,消息头,分隔行,消息体。
起始行
包括请求和应答两种。
请求格式:Method SP RTSP-Version CRLF
,Method包括:PLAY,PAUSE,TEARDOWN,这是RTSP中规定的控制子集,即播放、暂停和停止。
例:
PLAY MANSRTSP/1.0
CSeq:2
Range:npt=now-
头Scale
表示倍速播放;Pause Time
表示暂停位置(now表示当前位置);随机拖放使用Range
定位。
Scale至少支持: 0 .25 、0 .5 、1 、2 、4 ,Range表示播放录像起点的相对值,取值范围为0到播放录像的终点时间,参数以s为单位。
应答格式:Status-Line = RTSP-Version SP Status-Code SP Reason-PhraseCRLF
.
例:RTSP/1.0 200 OK
请求和应答的CSeq应该相等。
RTP协议
类似RTSP协议栈,国标的媒体传输通过RTP(RFC 3550)进行,并辅以RTCP控制(RFC 3550)。国标允许RTP传输基于PS封装的视音频数据或视音频基本流数据。即PS流或者H264/H265裸流。
需要注意的是2016版本的流封装里面还是没有H.265相关内容的,只有MPEG-4/H.264/SVAC这三种格式支持。
RTP的流媒体封装参考RFC即可,这里不再赘述。
编码标准
国标对各类设备的编码做了统一规定,需要保证全局唯一性。一般设备的国标编码是留空的,需要使用者自己设计。
SIP的URI格式是sip[s]:username@domain;uri-parameters
,名称一般就是国标编码,注意服务器一般也是使用国标编码命名的。RFC3261中对该格式进行详细规定。
domain一般就是ip+port,也可以使用域名,推荐端口是5060.
特别注意:国标规定SIP信令字符集是GB2312,而不是UTF-8.
通信流程
首先是设备注册(SIP register),如果设备或系统注册不成功,宜延迟一定的随机时间后重新注册。设备注册完成后设备与服务端之间就会建立长连接。注册完毕后设备会自动与服务器进行NTP对时,后续操作即可由服务端主动发起。
注册完成后,服务端即可主动发起请求。主要包括:
- 音视频实时点播(直播)
- 设备控制(PTZ、录像、报警设备的布防/撤防)
- 设备信息查询(包括设备ID、设备名、设备厂家名称、设备型号、设备地址、设备口令、设备类型、设备状态、设备安装地址、设备归属单位、父设备ID等信息)
- 历史音视频数据检索、回放、下载
- 语音广播、对讲
设备主动报送的信息包括:
- 告警信息
- 设备状态
注册/注销
设备注册/注销都要进行认证,支持口令和数字证书两种安全级别的认证。
在高安全级别下,使用sip+TLS进行加密传输。
SIP本身还要通过数字摘要做信令认证,算法如下:
在SIP的header中增加Date字段和Note字段,Note= (Digestnonce="",algorithm = ),算法包括MD5、SHA-1、SHA-256等常见算法。nonce 的 值 为 algorithm [From + to + CallId + Date+seed+ 消息体 ]数字摘要经过BASE64编码后的值,algorithm 的值为数字摘要的算法名称,“+”为字符串连接运算。Date应在校时精度范围之内,精度为秒,偏离值一般不超过10分钟。
具体的算法流程这里不再列出,就是常见的保证数据完整性的验证。
注册类似DHCP,是有时限的(默认1d,可以最短调到1h),到期之前需要设备自行重新注册。注册成功后设备就显示在线了。
基本注册(口令注册)流程如下:
- SIP代理向SIP服务器发送Register请求;
- SIP服务器向SIP代理发送响应401,并在响应的消息头WWW_Authenticate字段中给出适合SIP代理的认证体制和参数;
- SIP代理重新向SIP服务器发送Register请求,在请求的Authorization字段给出信任书,包含认证信息;
- SIP服务器对请求进行验证,如果检查出SIP代理身份合法,向SIP代理发送成功响应200 OK,如果身份不合法则发送拒绝服务应答;
基于数字证书的认证流程会复杂许多,这里不再详述。
注销和注册的流程其实类似,都用Register
方法,只是Expires设为0.
直播
服务端Invite之后,设备才开始推流,所以可以实现按需取流。
平台、设备媒体流保活机制规定如下:
-
链路建立后,码流经过的各级平台应具备媒体流丢失监测能力,若监测到媒体流丢失,应释放该条媒体链路,并通过会话内Bye消息通知上下级平台;
-
上下级平台之间、平台与设备之间、平台与客户端之间应通过注册、状态信息报送等进行状态监测,若监测到媒体流接收方或媒体流发送方故障或离线,应主动释放媒体链路,停止媒体流的发送;
-
通过Subject标识进行已发送流的清理判断。
-
上级平台向下级平台、平台向设备发送呼叫请求时,应携带Subject头域,Subject头域的“媒体流发送者ID:发送方媒体流序列号”用于对媒体源标识,此标识与请求的码流具有对应关系。
-
下级平台、设备在接收到呼叫请求后,应判断是否在发送以此媒体源标识的码流,若已经在发送,则应释放现有媒体流发送链路并按照请求建立新的媒体流发送链路。
-
点播流程可以由最终收流观看的客户端主动发起,也可以由SIP信令服务器自行发起。由于BS架构下,浏览器不支持RTP流直播,所以实际使用中。一般Invite请求是由SIP服务器发出,流推送到流媒体服务器之后,需要转协议才能给客户端进行观看。
设备控制
参考上述MANSCDP
部分,通过MESSAGE
消息实现。
部分命令不需要应答操作结果,只回答一个200 OK表示已经收到。包括:
- 相机、云台控制(PTZ)
- 远程启动
- 强制关键帧(IDR)
- 拉框放大/缩小
部分命令需要有明确的Response,包括:
- 录像控制
- 报警布防/撤防
- 报警复位
- 看守位控制
- 设备配置命令
注意,无论何种命令,对端都要有一个应答表示已收到(因为通信可能基于UDP)
报警事件
同样基于MANSCDP
扩展协议。
按着标准规定,报警事件需要服务端进行处理结果响应。
设备查询
标准提供了灵活的设备组织形式,允许按系统编码(级联时),行政区划,业务分组、虚拟组织等组织方式进行设备目录查询。设备目录查询的响应数据包括:命令类型(CmdType)、命令序列号(SN)、设备/区域/系统编码(De-viceID)、设备/区域/系统名称(Name)、设备状态(Status)、经度(Longitude)、纬度(Latitude)等。注意这里的SN不是设备序列号,而是命令序列号;用来异步映射请求和响应。
得到设备目录之后,可以对某个具体的设备进行消息查询。响应内容主要包括:命令类型(CmdType)、设备编码(DeviceID)、设备名称(DeviceName)、查询结果标志(Result)、厂商信息(Manufacturer)、设备型号(Model)、固件版本(Firm-ware)、最大支持摄像机个数(Channel)等。
设备状态可以单独进行查询,可以单独获取设备的状态标志位,包括:目标设备或区域或系统编码、命令类型(CmdType)、查询结果标志(Result)、是否在线(Online)、是否正常工作(Status)、不正常原因(Reason)、是否编码(Encode)、是否录像(Record)、设备时间和日期(DeviceTime)、报警设备状态列表(Alarmstatus)等,报警设备状态列表应包括报警设备或区域或系统编码(DeviceID)、报警设备状态(DutyStatus)等。
设备配置信息也可以单独查询,这里没有严格规定返回值内容。
设备预置位(云台)也可以单独查询,响应结果主要包括预置位编码(PresetID)、预置位名称(Pr-esetName)。
状态报送
类似报警事件,如果设备(网关、SIP设备、SIP客户端或联网系统)的状态异常,需要立即向SIP监控域的SIP服务器发送状态信息。即使无异常,也需要定时上报,即状态心跳(默认时间60s)。连续心跳丢失(默认超过3次)之后认为设备离线。
音视频文件检索
即回放检索功能,其检索条件包括区域、设备、录像时间段、录像地点、录像内容等。
响应内容可以分页返回,为了保证可靠,需要进行串行发送。即收到上一条的ack再进行下一页返回,服务端可以批量进行ack。
历史音视频回放
类似直播,也是Invite发起。不同的是,必须支持快进、暂停功能,相关控制见MANSRTSP
部分。
媒体播放完毕之后,发送端需要使用MESSAGE
消息通知服务器发送已经结束。如果服务端主动不看了,需要发送BYE通知设备端停止推流。
媒体下载
该功能类似回放,但是需要响应端给出文件大小相关参数方便计算下载进度。如果无法计算出大小,则需要根据码流中的时间来计算。
下载可以指定倍速,下载一旦开始就不能修改速度等参数,也不能暂停。
对时
如上所述,对时是在注册的时候完成的。为了避免时钟偏移,注册过期时间不宜过长。
订阅和发布
事件订阅包括报警事件、移动设备位置通知事件等。
设备目录订阅(对NVR)可以及时获取设备变更通知。
语音广播和语音对讲
这里假设终端设备(如IPC)具有语音广播能力,则可以利用gb协议进行双向语音广播。