我从去年8月份开始学习ONVIF,经历了各种困难,有时简直要暴走发狂,终于能够达成计划目标,实现了预订功能。痛苦已经过去,现在是写个问题总结的时候了,希望能记录遇到的问题,以便将来遗忘时参考。
测试的摄像头有2种品牌3种型号。分别是海康的2款枪机,DS-2CD3312D-I
台湾升泰科技(AVTECH)的一款家用IPC,AVM311
该问题比较奇怪,经过比较正常的应答包和错误包,发现是Header段缺少<wsadis:RelatesTo>内容。OnvifTest发出的probe命令带有MessageID,设备的应答报文必须带有该MessageID,只有一致才认为是正确的匹配。缺少<wsadis:RelatesTo>就导致OnvifTest工具软件无法识别应答命令。
跟踪服务端soap_serve()函数流程,发现问题出在soap_wsa_reply函数。
main()
soap_serve()
soap_serve_request()
soap_serve___wsdd__Probe()
__wsdd__Probe() wsddapi.c
soap_wsa_reply() wsddapi.c
该函数调用了插件函数soap_lookup_plugin()导致返回值无效,引起提前结束函数。这就引起没有填充Header段的RelatesTo结构体。
现在不清楚soap_lookup_plugin()为什么返回NULL,但是可以把对data的操作搬移到newheader->SOAP_WSA(RelatesTo)之后执行。这样回答的报文中就包含RelatesTo内容了。修改后的soap_wsa_reply()函数请看博客原文:
linux设备上的Onvif实现5:实现Probe命令检测设备:
http://gaohtao.blog.163.com/blog/static/58241823201362343648345/
我使用的Probe命令设定的等待时间是5s,在此期间等待摄像头应答,如果没有收到应答包就自动结束。收到应答包后解析出设备名和IP地址添加到设备列表中。实际上发现海康的摄像头只应答一次,升泰科技的摄像头摄像头应答3次。3次应答的MessageID完全相同。
发送的probe MessageID
<wsa:MessageID>41772060-76e5-11e1-ad77-85ac2d7785ac</wsa:MessageID>
收到的3次应答MessageID
(1) <wsa:MessageID>41772060-76e5-11e1-ad77-85ac2d7785ac</wsa:MessageID>
<wsa:RelatesTo>41772060-76e5-11e1-ad77-85ac2d7785ac</wsa:RelatesTo>
(2) <wsa:MessageID>41772060-76e5-11e1-ad77-85ac2d7785ac</wsa:MessageID>
<wsa:RelatesTo>41772060-76e5-11e1-ad77-85ac2d7785ac</wsa:RelatesTo>
(3) <wsa:MessageID>41772060-76e5-11e1-ad77-85ac2d7785ac</wsa:MessageID>
<wsa:RelatesTo>41772060-76e5-11e1-ad77-85ac2d7785ac</wsa:RelatesTo>
解决方法是在设备列表中增加UUID字段,添加设备时检查列表中是否存在UUID的设备,不存在就添加,存在就表示已经添加过了,丢弃此次应答包。具体实现时注意字符串比较函数最好使用strstr(),不要使用strcmp(),原因是输入参数可能分别是
urn:uuid:AA9DFBE7-4359-49D4-000E5324BB77,
AA9DFBE7-4359-49D4-000E5324BB77
这样的话strcmp就会出现错误。
我测试的2种牌子表现不同。海康摄像头直接调用该函数返回正确应答报文。升泰科技的摄像头则返回401错误,必须添加鉴权信息,然后再次调用GetCapabilities()才能正确返回应答报文。
添加鉴权过程研究了一段时间,终于弄明白了使用方法,请看博客原文:
Linux设备上的Onvif实现16:实现Onvif鉴权
http://gaohtao.blog.163.com/blog/static/58241823201383042530402/
使用GetProfiles()函数获取媒体信息,结果发现海康摄像头返回2份profile,升泰摄像头返回3份profile。这样在解析数据时出现遗漏错误。经过测试,发现升泰摄像头支持3个通道,这3份profile都是实际可用的,必须予以支持。
解决方法:扩展设备媒体信息数据结构,最多支持3个profile,对于超过3个的profile丢弃不处理。
<tt:VideoSourceConfigurationtoken="VS1token">
<tt:Name>VS1</tt:Name>
<tt:UseCount>3</tt:UseCount>
<tt:SourceToken>VS1srctoken</tt:SourceToken>
<tt:Bounds height="240"width="352" y="0" x="0"></tt:Bounds>
主通道:PROFILE-1
分辨率 Width=1280, Height=720
帧率 FrameRateLimit=30
码率 BitrateLimit=5000
编码 Encoding=H264
子通道 PROFILE-2
分辨率 Width=640, Height=480 设置正确的参数: 640x480
帧率 FrameRateLimit=30 25
码率 BitrateLimit=5000 1536
编码 Encoding=MPEG4 H264
子通道 PROFILE-3
分辨率 Width=320, Height=240
帧率 FrameRateLimit=30
码率 BitrateLimit=5000
编码 Encoding=H264
ONVIF协议的一个突出优点是可以使用命令修改摄像头的配置参数SetVideoEncoderConfiguration(),我就使用这个功能,把摄像头的视频流修改成我方设备支持的类型,结果海康摄像头工作正确,升泰摄像头命令应答成功,但是实际上摄像头参数根本没有修改。
解决方法:与台湾升泰公司的工程师联系,对方测试了这种情况,提供了摄像头的升级固件,更新后该问题解决。
RTSP协议中规定了2种鉴权方式,分别是基本认证(basic authentication)、摘要认证(digest authentication)。测试摄像头发现,有的摄像头应答DESCRIBE命令如下:
DESCRIBErtsp://192.168.0.112:540/live/h264_ulaw/VGA RTSP/1.0
CSeq: 2
Accept: application/sdp
User-Agent: ABB Genway E-3000 RTSP 1.0
RTSP/1.0 401 Unauthorized
CSeq: 2
WWW-Authenticate: Basicrealm="Server"
WWW-Authenticate: Digestrealm="Server", nonce="50e5d8bff60b6f5b5a85e7a5b613e85e
19512c53defbdfb69d9dd2739877cc82"
有的如下:
RTSP/1.0 401 Unauthorized
CSeq: 5
WWW-Authenticate: Digestrealm="8ce748eafc54",nonce="db83d3233626b950b519a1f658af4a47", stale="FALSE"
WWW-Authenticate: Basicrealm="8ce748eafc54"
Date: Tue, Dec 31 2013 11:55:39 GMT
就是这两种鉴权顺序是反的。不知道是否第一个代表了默认鉴权方式。以前实现了基本认证,后来努力了很久实现了摘要认证,请看博客原文:
Linux设备上的Onvif实现17:实现RTSP摘要认证
http://gaohtao.blog.163.com/blog/static/58241823201422711841555/
有的摄像头Web页面上可以设置鉴权方式(开关、基本、摘要),对应的在自己的RTSP代码中也要使用响应的方式,实例如下:
typedef struct _WWW_Authenticate
{
intbAuthenticate; //认证标记,0=none,1=basic, 2=digest
intnc; //请求计数
char realm[MAXPATH]; //认证的领域
char nonce[MAXPATH]; //服务器密码随机数
char cnonce[MAXPATH]; //客户端密码随机数
char qop[MAXPATH]; //保护质量
char username[MAXPATH]; //用户名
char password[MAXPATH]; //密码
char uri[MAXPATH]; //uri
char method[MAXPATH]; //method
char response[MAXPATH]; //摘要
char opaque[MAXPATH]; //?? ,用于客户端对服务端认证
char Authenticate[2*MAXPATH]; //存储完整的认证信息
}WWW_Authenticate;
if(ret == 401)
{
ret=simple_parse_www_authenticate(resp.www_authenticate1,resp.www_authenticate2);
if(count<=0)
{
if(ret==0) //WWW-Authenticate: Basic
{
rtsp_client->wwwAuthenticate.bAuthenticate = 1;
}
else if(ret==1) //WWW-Authenticate: Digest
{
rtsp_client->wwwAuthenticate.bAuthenticate = 2;
ParseDigestAuthenticateInfo(rtsp_client,resp.www_authenticate1, resp.www_authenticate2);
}
clear_response(&resp);
count++;
goto AUTHENTICATE;
}
else
{
RTSP_ALERT("--ERROR: not support www_authenticate! \n");
}
}
if(rtsp_client->wwwAuthenticate.bAuthenticate)
{
if(rtsp_client->wwwAuthenticate.bAuthenticate==2)
{
memset(rtsp_client->wwwAuthenticate.method, 0, MAXPATH);
strcpy(rtsp_client->wwwAuthenticate.method, "DESCRIBE");
memset(rtsp_client->wwwAuthenticate.uri, 0, MAXPATH);
strcpy(rtsp_client->wwwAuthenticate.uri, rtsp_client->url);
CreateDigestAuthenticate(rtsp_client,(u_char*)username,strlen(username));
}
else if(rtsp_client->wwwAuthenticate.bAuthenticate==1)
{
CreateBasicAuthenticate(rtsp_client,(u_char*)username,strlen(username));
}
cmd.authorization = malloc(2*MAXPATH);
memset(cmd.authorization, 0, 2*MAXPATH);
strncpy(cmd.authorization, rtsp_client->wwwAuthenticate.Authenticate,2*MAXPATH);
}
DESCRIBErtsp://192.168.0.112:540/live/h264_ulaw/VGA RTSP/1.0
CSeq: 3
Accept: application/sdp
Authorization: Basic YWRtaW46YWRtaW4=
User-Agent: ABB Genway E-3000 RTSP 1.0
RTSP/1.0 200 OK
CSeq: 3
Content-Base:rtsp://192.168.0.112:540/live/h264_ulaw/VGA/
Content-Type: application/sdp
Cache-Control: must-revalidate
x-Accept-Dynamic-Rate: 1
x-Accept-Dynamic-Rasave exit:isCheckpointed 1
te: 1 [not processing]
Content-Length: 305
response body length=305, current buffersize=305, offset=198
v=0
o=- 1 1 IN IP4 127.0.0.1
s=RTSP server
c=IN IP4 0.0.0.0
t=0 0
a=control:*
a=range:npt=now-
m=video 0 RTP/AVP 97
a=rtpmap:97 H264/90000
a=fmtp:97packetization-mode=1;profile-level-id=420028;sprop-parameter-sets=Z0IAKOkBQHsg,aM4xUg==;
a=control:track1
m=audio 0 RTP/AVP 0
a=control:track3
v-media = video
问题:其中a=control:track1,升泰摄像头数据与海康摄像头不一致,引起通道参数错误。
海康的应答结果有2种:
a=control:trackID=1 相对路径
a=control:rtsp://... 绝对路径
解决方法:由于a=control:字段内容不规范,为了通用考虑,不能检查control是否含有track/trackID,直接判断是否含有”rtsp://…”。
调试升泰摄像头遇到接收到的H264数据流无法解码显示,对H264的数据包进行了深入分析:
接收到的第一个数据包:
--len=43 80 e1 49 37 01 5f f7 5c 97 48 1d 18 78 0016 67 42 00 1e e9 01 40 7b 5c 48 00 6d dd 00 0c df e6 00 d8 81 09 40 00 04 68ce 31 52
解析如下:
80: cc(b4~b7)=0指示后面的CSRC个数=0,就是无CSRC
e1:Marker(b0)=1, payload type(b1~b7)=0x61
49 37:SequenceNumber
01 5f f7 5c: Timestamp
97 48 1d 18: SSRC―同步源标识
78: 表示STAP-A单一时间组合包,其后实际上包括sps、pps两帧
00 16 67 42 00 1e e901 40 7b 5c 48 00 6d dd 00 0c df e6 00 d8 81 09 40 sps帧长度=0x0016
00 04 68 ce 31 52 pps帧长度=0x0004
这里新遇到了STAP-A单一时间组合包,就是sps、pps两帧数据组合在一个数据包,以前没有实现过这种包的解析,所以无法显示。而海康的摄像头发出的SPS和PPS分别是单独的两个数据包,就能正常解析显示。
解决方法:增加解析STAP-A。
/*----------------- 处理一包是多帧------------------ */
if(24<=NALU_parload &&NALU_parload<=27)
{
switch(NALU_parload)
{
case 24: //STAP-A单一时间的组合包
{
/*带有rtp包头的数据内容示例 : 只包含sps/pps两帧
80 e1 49 37 01 5f f7 5c 9748 1d 18 78 00 16 67 42 00 1e e9 01 407b 5c 48 00 6d dd 00 0c df e6
*/
int i;
for(i=13;i<Packlength;)
{
int len; //内部一帧长度
len =(RtpPackage[i]<<8)+RtpPackage[i+1];
char * frameBuf= (char*)malloc(len);
memset(frameBuf,0, len);
memcpy(frameBuf,RtpPackage+i+2,len);
OSA_DBG_MSG("ReciveRTPPackage() STAP-A: framelen=%d \n",len);
DoH264SinglePackage(frameBuf,len);
i= i+2+len;
free(frameBuf);
}
}
break;
case 25: //STAP-B单一时间的组合包,没有实现解析
case 26: //MTAP16多个时间的组合包
case 27: //MTAP24多个时间的组合包
default:
SliceReady=-1;
break;
}
goto NALU_end;
}
9 分机设备打开摄像头,经常收不到摄像头的视频流,这时所有的RTSP命令都返回454错误。
升泰摄像头遇到了这种情况,好好的在自己的设备上监视摄像头成功,停止,再一次监视就无法收到数据流。这时RTSP命令都返回错误。
5 ==================================
PLAYrtsp://192.168.0.112:540/live/h264_ulaw/VGA RTSP/1.0
CSeq: 5
Authorization: Basic YWRtaW46YWRtaW4=
Session: C3ED883FFE905F1816A2D1EC98DC5B
User-Agent: ABB Genway E-3000 RTSP 1.0
RTSP/1.0 200 OK
CSeq: 5
Session: C3ED883FFE905F1816A2D1EC98DC5B
6 ==================================
TEARDOWNrtsp://192.168.0.112:540/live/h264_ulaw/VGA RTSP/1.0
CSeq: 6
Authorization: Basic YWRtaW46YWRtaW4=
Session: 2D50C2A8C72EC5AB5605D6906FCD26
User-Agent: ABB Genway E-3000 RTSP 1.0
RTSP/1.0 454 Session Not Found
CSeq: 6
complete recv response, buffer state:offset=43 len=0
rtsp respond code(454) != 200
遇到的问题:
停止命令失败,提示会话不存在。
2014-3-11 发现真正原因:
接收流程:分机设备向摄像头发送PLAY命令,收到成功应答(200 OK)后才开始建立rtp、rtcp socket连接,接收H264数据流。
分析故障时的网络抓包序列:
Package 26:分机向摄像头发送PLAY命令
Package 28:摄像头应答200 OK
Package 30: 摄像头发出第一个数据包,实际内容就是SPS、PPS的组合包。
Package 32:摄像头发出第二个数据包,内容是I帧。
Package 33:分机应答Portunreachable,之后摄像头就停止了发送数据。
原因分析:
通过网络上对“Portunreachable”的解释,发现如果摄像头向分机端口发送UDP数据包时,分机端还未能建立RTP/RTCP,就可能出现分机的RTP端口无法接收数据,分机linux系统自动应答ICMP数据包Port unreachable, 该摄像头连续收到2个ICMP包就会中断连接。由于之前SETUP建立的连接被中断,之后分机发送TEARDOWN提示“Session Not Found”。
奇怪的是海康摄像头就不会停止发送数据,也不会中断连接,这样当分机端RTP连接建立成功后就能收到数据包了。
解决方法:
1 修改流程:分机收到摄像头SETUP成功应答后就要建立UDP接收线程,然后才能发送PLAY请求。
2 为了确保UDP接收线程中的socket初始化完成后才能发送PlayReq,在IPC_CreateEngineThread线程中使用信号量wait,在RTP_ReciveThread线程中发出post。
这样处理之后分机监视正常,反复操作100次全部成功显示。