概览
本文记录使用IP直连的方式从海康摄像头获取PS流的过程及注意事项。本例模拟了一个SIP Server(UAS),设置摄像头注册到上面,之后模拟了一个 SIP Client(UAC)向摄像头的IP地址发送获取视频的请求,之后监听UDP的端口来接收摄像头发送过来的PS流。
先行知识
1,了解 SIP 基本概念
2,会使用通用编程语言收发UDP包
3,通读国标《GB/T 28181-2016》文档
一些概念
1,SIP 会话初始协议栈规定了一套建立通信的流程,国标 GB28181 基于 SIP 来构建并添加规范。
2,通过 GB28181 标准,就可以调用同样支持 GB28181 标准的监控设备(IP Camera,NVR等)
3,海康网络摄像头需要连接到SIP服务器,并且需要保持心跳,否则推送的PS流会断。
我的环境
IP Camera(IPC):192.168.42.128
服务器(UAS):192.168.3.189
我的电脑(UAC):192.168.103.232
主要流程
1,编写模拟的 SIP Server,功能:提供注册和心跳的应答。
2,开启海康IPC的GB28181支持,填写SIP服务并设置IP白名单。
3,启动一个程序来监听端口,用于接收IPC返回的数据。
4,编写 SIP Client,向IPC发起 INVITE 请求,并对IPC返回的SIP消息进行应答。
模拟 SIP Server
此处基于 Pjsua2 实现
代码:【整理后补充】
我的代码是从这里精简出来的:https://github.com/tomyhometown/PjsipServerAndClient
需要先完成 Pjsip/Pjsua2 环境的搭建:《Ubuntu 18.04 下编译 Pjsua2》
按照 GB28181 的流程,SIP Server 需要对设备进行验证,然后才能允许其注册,不过这里可以取巧,就是不进行验证,IPC注册就返回注册成功,心跳也是。
static pj_bool_t default_mod_on_rx_request(pjsip_rx_data *rdata)
{
pjsip_tx_data *tdata;
// 判断如果发送过来的数据为注册类型进行相应处理
if (pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, &pjsip_register_method) == 0)
{
// 解析认证请求
// aregistrar(rdata);
PJ_LOG(3, (THIS_FILE, "REGISTRATAR\n\n\n"));
pjsip_endpt_create_response(pjsua_get_pjsip_endpt(), rdata, 200, NULL, &tdata);
pjsip_endpt_send_response2(pjsua_get_pjsip_endpt(), rdata, tdata, NULL, NULL);
return PJ_TRUE;
}
// 其它情况如心跳,直接返回 200 OK
pjsip_endpt_create_response(pjsua_get_pjsip_endpt(), rdata, 200, NULL, &tdata);
pjsip_endpt_send_response2(pjsua_get_pjsip_endpt(), rdata, tdata, NULL, NULL);
return PJ_TRUE;
}
编译使用 qmake 从 server.pro 文件生成 Makefile
qmake -o Makefile server.pro
使用 ./server
启动服务
引申:关于认证算法(本例未使用认证)
# === 伪代码 ===
username = "34020000001110000001"
passed = "12345678"
realm = "34020000"
nonce = "awer23sdfj123123"
uri = "sip:192.168.103.245"
Method = "REGISTER"
# HA1=MD5(username:realm:passwd)
HA1 = MD5(34020000001110000001:34020000:12345678)
# HA2 = MD5(Method:Uri)
HA2 = MD5(REGISTER:sip:192.168.103.245)
HA1 = d4a15a944c6af0405cc244114fea3da0
HA2 = 948e32c0c2723059c74f8392c1f9e4db
# response = MD5(d4a15a944c6af0405cc244114fea3da0:awer23sdfj123123:948e32c0c2723059c74f8392c1f9e4db)
# 最终的 response 值
response = 3d988c33c15d780273d74577a6a3319e
配置海康摄像头
接下来进入海康摄像头的配置页面
启用 GB28181 的平台接入方式,SIP 服务器ID 填写 SIP 服务的用户 Uri,地址填写运行刚才的模拟 SIP Server 的IP 地址。
过一会儿后,注册状态会变为“在线”。
如果在终端看日志,可以看到如下的注册及心跳包内容。
注册及回复
心跳及回复
此时,海康摄像头已经接入到我们模拟的 GB28181 平台了
不要忘记设置白名单,添加 SIP Client 的IP 地址,否则发送的 INVITE 请求不会进行响应
启动 UDP 监听端口
此时在客户端上启动一个监听 UDP 端口的程序,无论用什么语言编写,监听本地的 IP 及 端口(6000),然后把收到的内容打印即可。
备注:6000 端口是即将发送请求时里面指定的,意思是说我用 6000/udp 端口号来接收 ps 流数据。
SIP Client 与摄像头交互
流程如下
1)首先客户端发送 INVITE 指令
INVITE sip:34020000001320000001@192.168.42.128:5060 SIP/2.0
Via: SIP/2.0/UDP 192.168.103.232:5060;rport;branch=z9hG4bKPj952ad841-0e25-454c-a337-97c39b0b6bbb
Max-Forwards: 70
From: sip:1001@192.168.103.232;tag=9c72e4b3-f845-4510-b7ac-f74169da268d
To: sip:34020000001320000001@192.168.42.128
Contact: <sip:1001@192.168.103.232:5060;ob>
Call-ID: 3d23433e-0392-42e0-8d7b-9d3d692eab07
CSeq: 12342 INVITE
Allow: PRACK, INVITE, ACK, BYE, CANCEL, UPDATE, INFO, SUBSCRIBE, NOTIFY, REFER, MESSAGE, OPTIONS
Supported: replaces, 100rel, timer, norefersub
Session-Expires: 1800
Min-SE: 90
User-Agent: sip-client
Subject: 34020000001320000001:0,1001:0
Content-Type: application/sdp
Content-Length: 142
v=0
o=1001 0 0 IN IP4 192.168.103.232
s=Play
c=IN IP4 192.168.103.232
t=0 0
m=video 6000 RTP/AVP 96
a=recvonly
a=rtpmap:96 PS/90000
需要注意的地方:
1,SIP 命令中的 From: 下的 tag 值要动态生成,后续应答也会用到这个tag。
2,本例中客户端的用户名和ID使用的为“1001”,这个值是随便取的,没有问题。
3,要有 Subject 头,这个是“摄像头通道ID:通道号,通道ID(主叫的sip id):通道号”
4,SDP 中的 IP 地址指明接收方地址,以及 m
下的 6000 端口用于接收
5,媒体类型未 96, PS/90000
2)IPC 返回 Trying 数据
SIP/2.0 100 Trying
Via: SIP/2.0/UDP 192.168.103.232:5060;rport=5060;branch=z9hG4bKPj952ad841-0e25-454c-a337-97c39b0b6bbb
From: <sip:1001@192.168.103.232>;tag=9c72e4b3-f845-4510-b7ac-f74169da268d
To: <sip:34020000001320000001@192.168.42.128>
Call-ID: 3d23433e-0392-42e0-8d7b-9d3d692eab07
CSeq: 12342 INVITE
User-Agent: IP Camera
Content-Length: 0
3)接下来 IPC 返回 200 OK
SIP/2.0 200 OK
Via: SIP/2.0/UDP 192.168.103.232:5060;rport=5060;branch=z9hG4bKPj952ad841-0e25-454c-a337-97c39b0b6bbb
From: <sip:1001@192.168.103.232>;tag=9c72e4b3-f845-4510-b7ac-f74169da268d
To: <sip:34020000001320000001@192.168.42.128>;tag=38473588
Call-ID: 3d23433e-0392-42e0-8d7b-9d3d692eab07
CSeq: 12342 INVITE
Contact: <sip:34020000001320000001@192.168.42.128:5060>
Content-Type: application/sdp
User-Agent: IP Camera
Content-Length: 189
v=0
o=34020000001320000001 208 208 IN IP4 192.168.42.128
s=Play
c=IN IP4 192.168.42.128
t=0 0
m=video 15060 RTP/AVP 96
a=setup:active
a=sendonly
a=rtpmap:96 PS/90000
a=filesize:0
注意的地方:
1,SIP 中的 To 字段 tag 标签,ACK 或者 BYE 进行响应的时候要携带着。
4)SIP Client 响应 ACK
ACK sip:34020000001320000001@192.168.42.128:5060 SIP/2.0
Via: SIP/2.0/UDP 192.168.103.232:5060;rport;branch=z9hG4bKPj4926c10e-b480-4cb0-a520-fdbf694c5f9e
Max-Forwards: 70
From: sip:1001@192.168.103.232;tag=9c72e4b3-f845-4510-b7ac-f74169da268d
To: sip:34020000001320000001@192.168.42.128;tag=38473588
Call-ID: 3d23433e-0392-42e0-8d7b-9d3d692eab07
CSeq: 12343 ACK
Content-Length: 0
接下来 IP Camera 就开始推送 RTP 承载的 PS 流数据了
此时再看之前监听 6000 端口号的程序输出,就可以看到有数据持续输出。
另外注意:
1,IPC 需要在 SIP 注册并维持心跳,否则流推没几秒钟就断了。
2,如果 UDP 端口没有监听,那么会在 Wireshark 中看到 IPC 发送提示以及 BYE 指令。
另外就是,SIP Client 做的其实很简单,就是把字符串拼出来,然后通过 UDP 来发送出去,换句话说,无论用 Rust,Python 无论什么语言,怎么拼,最后包发出去,Wireshark 抓包查看,流程和返回内容一致,就OK了。
这里是我测试用的 Rust 编写的例子:【整理后补充】
总结
1,一定要用 Wireshark 多抓包,多看,底层的数据包,错一个字节可能代表的意义就不一样了
2,自己模拟写 SIP Client,或者 SIP Server,做简单的功能和验证很方便,但是如果功能复杂,那还是要基于 Pjsip,Pjsua2 来开发,不过这个库的函数是真多,文档也不友好...
3,本例演示了 SIP Client 作为客户端直连 IPC 获取 PS 流数据,其实根据 GB28181 正常流程,SIP Client 是要发送请求到 SIP Server 的,SIP Server 作为背靠背代理来进行请求,之后数据再发过来,因为 SIP Server 是模拟的,很多功能是没有的,所以直接发往了 IPC。
参考