rtsp client发送DESCIBE的时候,server响应的数据为sdp数据格式,sdp包含了音视频数据的信息。客户端发送“DESCRIBE”,同时Accept头为“application/sdp”。
server响应“DESCRIBE”请求,其中body的内容为sdp数据格式。
DESCRIBE rtsp://192.168.1.65:554/cam/realmonitor?channel=1&subtype=0 RTSP/1.0
CSeq: 7
Authorization: Digest username="admin", realm="IP Camera(G8735)", nonce="9790c5947f6ad1b894f5d68edd4715b2", uri="rtsp://192.168.1.65:554/cam/realmonitor?channel=1&subtype=0", response="ee47399fd2cf6fefd01be4527f784c21"
User-Agent: LibVLC/3.0.16 (LIVE555 Streaming Media v2016.11.28)
Accept: application/sdp
RTSP/1.0 200 OK
CSeq: 7
Content-Type: application/sdp
Content-Base: rtsp://192.168.1.65:554/cam/realmonitor/
Content-Length: 625
v=0
o=- 1656081731594734 1656081731594734 IN IP4 192.168.1.65
s=Media Presentation
e=NONE
b=AS:5050
t=0 0
a=control:rtsp://192.168.1.65:554/cam/realmonitor/?channel=1&subtype=0
m=video 0 RTP/AVP 96
c=IN IP4 0.0.0.0
b=AS:5000
a=recvonly
a=x-dimensions:1920,1080
a=control:rtsp://192.168.1.65:554/cam/realmonitor/trackID=1?channel=1&subtype=0
a=rtpmap:96 H264/90000
a=fmtp:96 profile-level-id=420029; packetization-mode=1; sprop-parameter-sets=Z00AKpY1QPAET8s3AQEBQAABwgAAV+QB,aO4xsg==
a=Media_header:MEDIAINFO=494D4B48010300000400000100000000000000000000000000000000000000000000000000000000;
a=appversion:1.0
sdp由多行”<type>=<value>”组成,其中<type>是一个字符串,<value>是一个字符串,type表示类型,value的格式视type而定,整个协议区分大小写,”=”两侧不允许有空格
sdp会话描述包含一个会话级描述(session_level_description)和多个媒体级描述(media_level description)组成!会话级描述的作用域是整个会话,其位置从”v=”行开始到第一个媒体描述为止;媒体级描述是对单个的媒体流进行描述,如传输过程中的视频流信息,从”m=”开始到下一个媒体描述为止,如下图所示。
根据server返回的body数据,我们可以得知会话级描述包含如下内容
v=0
o=- 1656081731594734 1656081731594734 IN IP4 192.168.1.65
s=Media Presentation
e=NONE
b=AS:5050
t=0 0
a=control:rtsp://192.168.1.65:554/cam/realmonitor/?channel=1&subtype=0
其中
v=<version>
o=<username> <session id> <version> <network type> <address type> <address>
s=<session name>
e=<email address>
b=<modifier>:<bandwidth-value>
t=<start time> <stop time>
a=<attribute>:<value>
而媒体描述的信息如下
m=video 0 RTP/AVP 96
c=IN IP4 0.0.0.0
b=AS:5000
a=recvonly
a=x-dimensions:1920,1080
a=control:rtsp://192.168.1.65:554/cam/realmonitor/trackID=1?channel=1&subtype=0
a=rtpmap:96 H264/90000
a=fmtp:96 profile-level-id=420029; packetization-mode=1; sprop-parameter-sets=Z00AKpY1QPAET8s3AQEBQAABwgAAV+QB,aO4xsg==
a=Media_header:MEDIAINFO=494D4B48010300000400000100000000000000000000000000000000000000000000000000000000;
a=appversion:1.0
其中各字段含义如下:
m=<media> <port> <transport> <fmt list>
c=<network type> <address type> <connection address>
b=<modifier>:<bandwidth-value>
a=<attribute>:<value>
我们可以看到在会话描述级中和媒体描述中均存在a=control:,分别为
a=control:rtsp://192.168.1.65:554/cam/realmonitor/?channel=1&subtype=0
a=control:rtsp://192.168.1.65:554/cam/realmonitor/trackID=1?channel=1&subtype=0
这里的两个URL分别主要用到RTSP协议中PLAY、TEARDOWN和SETUP方法请求中,也就是说,当请求为PLAY或者TEARDOWN的时候采用会话中的URL,也就是a=control:rtsp://192.168.1.65:554/cam/realmonitor/?channel=1&subtype=0
当请求方法为SETUP的时候,采用媒体描述中的URL,也就是a=control:rtsp://192.168.1.65:554/cam/realmonitor/trackID=1?channel=1&subtype=0
这也就是我们后面看到SETUP和PLAY的URL不一样的原因,主要是采用sdp中的字段属性决定。也就如下抓包的数据。
SETUP方法
SETUP rtsp://192.168.1.65:554/cam/realmonitor/trackID=1?channel=1&subtype=0 RTSP/1.0
CSeq: 8
Authorization: Digest username="admin", realm="IP Camera(G8735)", nonce="9790c5947f6ad1b894f5d68edd4715b2", uri="rtsp://192.168.1.65:554/cam/realmonitor/", response="d064e6136d51a63c8e77a9b9e61ea942"
User-Agent: LibVLC/3.0.16 (LIVE555 Streaming Media v2016.11.28)
Transport: RTP/AVP;unicast;client_port=55122-55123
PLAY方法
PLAY rtsp://192.168.1.65:554/cam/realmonitor/?channel=1&subtype=0 RTSP/1.0
CSeq: 9
Authorization: Digest username="admin", realm="IP Camera(G8735)", nonce="9790c5947f6ad1b894f5d68edd4715b2", uri="rtsp://192.168.1.65:554/cam/realmonitor/", response="1658da5ce39c8e3b07f470c809a171f9"
User-Agent: LibVLC/3.0.16 (LIVE555 Streaming Media v2016.11.28)
Session: 1236328428
Range: npt=0.000-
如下代码实现对sdp数据中的会话url和媒体url的解析。
int sdp_info_parse(char *sdp_body,SDP_INFO_ST *p_sdp_info)
{
int session_or_media = 0;
char *p = NULL;
char sdp_key;
char sdp_value[256];
SDP_MEDIA_TYPE sdp_media_type = SDP_UNKOWN_MEDIA;
if(NULL == sdp_body || NULL == p_sdp_info){
return -1;
}
//根据\n进行分行
printf("[%s:%d]\n",__FUNCTION__,__LINE__);
p = strtok(sdp_body,"\n");
while(p != NULL){
memset(sdp_value,0x00,sizeof(sdp_value));
printf("sdp key is %c\n",sdp_key);
sscanf(p,"%c=%s",&sdp_key,sdp_value);
if(sdp_key == 'm'){
session_or_media = 1;
//解析media 目前只解析video & audio模块
if(strstr(sdp_value,"video") != NULL){
sdp_media_type = SDP_VIDEO_MEDIA;
}
else if(strstr(sdp_value,"audio") != NULL){
sdp_media_type = SDP_AUDIO_MEDIA;
}
else{
sdp_media_type = SDP_OTHER_MEDIA;
}
}
if(session_or_media == 1){
//media info
if(sdp_media_type == SDP_VIDEO_MEDIA){
if(sdp_key == 'a'){
if(strncmp(sdp_value,"control:",strlen("control:")) == 0){
memcpy(p_sdp_info->sdp_media_info.sdp_control,sdp_value+strlen("control:"),strlen(sdp_value)-strlen("control:"));
}
}
}
}else{
//session info
if(sdp_key == 'v'){
memcpy(p_sdp_info->sdp_session_info.sdp_v,sdp_value,strlen(sdp_value));
}
if(sdp_key == 'a'){
if(strncmp(sdp_value,"control:",strlen("control:")) == 0){
memcpy(p_sdp_info->sdp_session_info.sdp_control,sdp_value+strlen("control:"),strlen(sdp_value)-strlen("control:"));
}
}
}
p = strtok(NULL,"\n");
}
return 0;
}