RTSP协议,个人觉得就是http协议的扩展,或者就是HTTP协议的一个演变,为什么这么说,因为根据RTSP协议的协议格式,可以发现,其与HTTP协议格式是相同的,在HTTP协议中,我们常用的请求方法有GET、POST、PUT等,然而RTSP协议中,方法有OPTIONS、DESCRIBE、SETUP、PLAY、TEARDOWN。
如下,对应的是RTSP请求和应答数据格式,具体格式解析,我们可以通过wireshark抓一包RTSP OPTIONS请求的数据包进行解析。
图1.1 RTSP请求数据格式
图1.2 RTSP应答数据格式
RTSP OPTIONS请求以及应答数据
其中请求方法为 :OPTIONS
URL为:rtsp://192.168.1.65:554/cam/realmonitor?channel=1&subtype=0
协议版本:RTSP/1.0
切记勿忘“回车符”“换行符”-------------"\r\n"
应答协议版本:RTSP/1.0
状态码:200
状态短语:OK
OPTIONS请求的含义如下:
C->S 客户端向服务器发送OPTIONS请求,OPTIONS请求主要用于请求服务器有哪些方法。
S->C 服务器应答客户端,并返回当前可用的方法。这些方法主要包含OPTIONS、DESCRIBE、SETUP、PLAY、PAUSE、TRARDOWN。
int rtsp_client_send_option(rtsp_ctx *p_rtsp_ctx)
{
if(NULL == p_rtsp_ctx){
return -1;
}
if(p_rtsp_ctx->auth.scheme == HTTP_AUTH_NONE){
return rtsp_client_send_handler(p_rtsp_ctx,"OPTIONS",NULL,NULL,0);
}
else if(p_rtsp_ctx->auth.scheme == HTTP_AUTH_DIGEST){
}
return -1;
}
int rtsp_client_send_handler(rtsp_ctx *rtsp_session_ctx,const char *method,const char *header,const void *body,int body_len)
{
int len;
int ret = 0;
if(NULL == rtsp_session_ctx){
printf("[%s:%d]rtsp session ctx is null\n",__FUNCTION__,__LINE__);
return -1;
}
memset(rtsp_session_ctx->send_buf,0x00,MAX_RTSP_PACKAGE_SIZE);
//
len = snprintf(rtsp_session_ctx->send_buf,MAX_RTSP_PACKAGE_SIZE,
"%s %s RTSP/1.0\r\n"
"CSeq: %d\r\n",
method,rtsp_session_ctx->uri,rtsp_session_ctx->cseq+1);
if(len > 0 && header){
len += snprintf(rtsp_session_ctx->send_buf + len,MAX_RTSP_PACKAGE_SIZE-len,"%s",header);
}
if(len > 0){
len += snprintf(rtsp_session_ctx->send_buf + len ,MAX_RTSP_PACKAGE_SIZE - len,"Content-Length: %d\r\n\r\n",body_len);
}
if(body_len > 0){
memcpy(rtsp_session_ctx->send_buf+len,body,body_len);
}
printf("send buf [%s]\n",rtsp_session_ctx->send_buf);
ret = write(rtsp_session_ctx->fd,rtsp_session_ctx->send_buf,len+body_len);
return ret;
}
也就是调用OPTIONS请求的时候我们下发的数据为:
OPTIONS URL RTSP/1.0\r\n
CSeq: x\r\n
\r\n
当我们实现发送OPTIONS请求的时候,同时还应该监听socket获取server的应答,对于OPTIONS方法来说,监听server的应答,可以获取到server端支持那些方法(OPTIONS DESCRIBE SETUP PLAY TEARDOWN)。
这里我们采用epoll对socket句柄进行监听,当接受到服务器发送的数据后,我们对服务器发送的数据进行解析。
int epoll_fd = -1;
struct epoll_event ev;
struct epoll_event event[MAX_CONNECT_NUM] = {{EPOLLIN}};
int register_handle_into_epoll(int fd)
{
if(epoll_fd < 0){
return -1;
}
ev.data.fd = fd;
ev.events = EPOLLIN;
if(epoll_ctl(epoll_fd,EPOLL_CTL_ADD,fd,&ev) < 0){
return -1;
}
return 0;
}
int unregister_handle_into_epoll(int fd)
{
if(epoll_fd < 0){
return -1;
}
if(epoll_ctl(epoll_fd,EPOLL_CTL_DEL,fd,NULL) < 0){
return -1;
}
return 0;
}
int init_epoll_create()
{
return epoll_create1(EPOLL_CLOEXEC);
}
通过接受线程实现对服务器响应数据进行处理。
/*
* rtsp client 接受服务端发送的数据处理逻辑,解析RTSP SERVER发送的 OPTIONS
* DESCRIBE、PLAY等应答,如果应答返回401则表示需要认证,目前我们仅支持摘要认证
*/
void *rtsp_client_rcv_pthread(void *args)
{
int event_cnt = 0;
int i = 0;
char buf[2048];
int n = 0;
rtsp_ctx *p_rtsp_ctx_info = (rtsp_ctx*)args;
while(1){
event_cnt = epoll_wait(epoll_fd,event,sizeof(event)/sizeof(*event),1000);
for(i = 0;i < event_cnt;i++){
if(event[i].events & EPOLLIN){
memset(buf,0x00,sizeof(buf));
n = read(event[i].data.fd,buf,sizeof(buf));
if(errno == ECONNRESET || n == 0){
unregister_handle_into_epoll(event[i].data.fd);
}else{
printf("rcv [%s]\n",buf);
//这里是接受到的数据进行处理函数,OPTIONS DESCRIBE SETUP PLAY TEARDOWN 响应处理函数
rtsp_client_rcv_handler_proc(buf,p_rtsp_ctx_info);
}
}
else if((event[i].events & EPOLLHUP) || (event[i].events & EPOLLRDHUP))
{
unregister_handle_into_epoll(event[i].data.fd);
}
}
}
}
数据解析处理函数
int rtsp_client_rcv_handler_proc(unsigned char *rcv_http_string,rtsp_ctx *p_rtsp_ctx)
{
parse_first_line(rcv_http_string,&(p_rtsp_ctx->http));
parse_http_header(rcv_http_string,p_rtsp_ctx);
parse_http_body(rcv_http_string,p_rtsp_ctx);
switch (p_rtsp_ctx->e_state)
{
case RTSP_OPTION:
printf("option proc\n");
rtsp_client_on_option_reply(p_rtsp_ctx);
break;
case RTSP_DESCRIBE:
printf("describe proc\n");
rtsp_client_on_describe_reply(p_rtsp_ctx);
break;
case RTSP_SETUP:
printf("setup proc\n");
rtsp_client_on_setup_reply(p_rtsp_ctx);
break;
case RTSP_PLAY:
printf("play proc\n");
rtsp_client_on_play_reply(p_rtsp_ctx);
break;
default:
break;
}
}
parse_first_line用于解析RTSP协议响应的第一行,获取状态码。状态码有多种,具体可以参考HTTP协议,这里我们主要用到了200和401(未认证)。
RTSP/1.0 200 OK
int parse_first_line(unsigned char *http_string,struct http_parse *http)
{
unsigned char req_first_line[128];
unsigned char *p_line_start = NULL;
unsigned char *p_line_end = NULL;
int ret = 0;
if(NULL == http_string || NULL == http){
printf("[%s:%d]param is null\n",__FUNCTION__,__LINE__);
return -1;
}
memset(req_first_line,0x00,sizeof(req_first_line));
p_line_start = http_string;
p_line_end = strstr(http_string,"\r\n");
if(NULL == p_line_end){
return -1;
}
memcpy(req_first_line,http_string,p_line_end-p_line_start);
printf("req first line is [%s]\n",req_first_line);
/**method http_code http_result**
protocol 200 OK**/
ret = sscanf(req_first_line,"%s %d %s",http->http_protocol,&(http->http_code),http->http_result);
if(ret < 0){
return -1;
}
return 0;
}
parse_http_header主要用于解析RTSP的头部数据,对于OPTIONS的应答,主要解析以下字段。
CSeq: 2
Public: OPTIONS, DESCRIBE, PLAY, PAUSE, SETUP, TEARDOWN, SET_PARAMETER, GET_PARAMETER
Date: Fri, Jun 24 2022 14:42:01 GMT
int parse_http_header(unsigned char *http_string,rtsp_ctx *p_rtsp_ctx)
{
unsigned char *p_http_header_start = NULL;
unsigned char *p_http_header_end = NULL;
unsigned char http_header_info[512];
char *p = NULL;
char *p1 = NULL;
char *p2 = NULL;
char *p3 = NULL;
char *outer_ptr = NULL;
char *inner_ptr = NULL;
if(NULL == http_string || NULL == p_rtsp_ctx){
return -1;
}
p_http_header_end = strstr(http_string,"\r\n\r\n");
if(NULL == p_http_header_end){
return -1;
}
p_http_header_start = strstr(http_string,"\r\n");
if(NULL == p_http_header_start){
return -1;
}
p_http_header_start += strlen("\r\n");
memset(http_header_info,0x00,sizeof(http_header_info));
memcpy(http_header_info,p_http_header_start,p_http_header_end-p_http_header_start);
printf("http header info[%s]\n",http_header_info);
//每行进行解析,解析CSeq Authorization等信息
p = strtok_r(http_header_info,"\r\n",&outer_ptr);
while(p){
printf("strtok [%s]\n",p);
if(strncmp(p,"CSeq:",strlen("CSeq:")) == 0){
p_rtsp_ctx->cseq = atoi(p+strlen("CSeq:"));
}else if(strncmp(p,"WWW-Authenticate:",strlen("WWW-Authenticate:")) == 0){
//解析认证信息
//解析是不是Digest认证
p1 = strstr(p,"Digest");
if(NULL != p1){
p1 += strlen("Digest") + 1; //1表示空格
//根据“,”进行解析 realm nonce 信息
p2 = strtok_r(p1,",",&inner_ptr);
while(p2){
printf("p2 =====================[%s]\n",p2);
if((p3 = strstr(p2,"realm=")) != NULL){
p3 += strlen("realm=");
memcpy(p_rtsp_ctx->auth.realm,p3+1,strlen(p3)-2);
}else if((p3 = strstr(p2,"nonce=")) != NULL){
p3 += strlen("nonce=");
memcpy(p_rtsp_ctx->auth.nonce,p3+1,strlen(p3)-2);
}
p2 = strtok_r(NULL,",",&inner_ptr);
}
p_rtsp_ctx->auth.scheme = HTTP_AUTH_DIGEST;
}
}else if(strncmp(p,"Transport:",strlen("Transport:")) == 0){
//根据;分隔符解析udp server port
p1 = p+strlen("Transport:");
p2 = strtok_r(p1,";",&inner_ptr);
while(p2){
printf("********p2 is %s\n",p2);
if((p3 = strstr(p2,"server_port=")) != NULL){
//rtp UDP
p3 += strlen("server_port=");
printf("server rtp port is %s\n",p3);
if(p_rtsp_ctx->net_info.tcp_udp_type == 0){
sscanf(p3,"%d-%d",&(p_rtsp_ctx->net_info.server_rtp_port),&(p_rtsp_ctx->net_info.server_rtcp_port));
printf("rtp [%d] rtcp [%d]\n",p_rtsp_ctx->net_info.server_rtp_port,p_rtsp_ctx->net_info.server_rtcp_port);
}else if(p_rtsp_ctx->net_info.tcp_udp_type == 1){
//rtp TCP
}
}
p2 = strtok_r(NULL,";",&inner_ptr);
}
}else if(strncmp(p,"Session:",strlen("Session:")) == 0){
p1 = p + strlen("Session:");
p2 = strtok_r(p1,";",&inner_ptr);
while(p2){
if((p3 =strstr(p2,"=")) != NULL){
}else{
//session id
memcpy(p_rtsp_ctx->session_id,p2,strlen(p2));
printf("session id is %s\n",p_rtsp_ctx->session_id);
}
p2 = strtok_r(NULL,";",&inner_ptr);
}
}else if(strncmp(p,"Content-Length:",strlen("Content-Length:")) == 0){
p_rtsp_ctx->http.content_len = atoi(p+strlen("Content-Length:")+1);
printf("p is %s\n",p);
printf(" p_rtsp_ctx->http.content_len is %d\n", p_rtsp_ctx->http.content_len);
//sscanf(p,"\"%d-%d\"",&(p_rtsp_ctx->net_info.server_rtp_port),&(p_rtsp_ctx->net_info.server_rtcp_port));
}
p = strtok_r(NULL,"\r\n",&outer_ptr);
}
return 0;
}
parse_http_body用于获取RTSP响应body的数据,这里主要是后面DESCIBE的时候获取sdp数据。
int parse_http_body(unsigned char *http_string,rtsp_ctx *p_rtsp_ctx)
{
unsigned char *body_start_adrr = NULL;
if(NULL == http_string || NULL == p_rtsp_ctx)
return -1;
if(p_rtsp_ctx->http.content_len > 0){
//获取body start addr
if(p_rtsp_ctx->http.content_len > 2048){
printf("[%s:%d]content len more than 2048!!!!!!\n",__FUNCTION__,__LINE__);
return -1;
}
body_start_adrr = strstr(http_string,"\r\n\r\n");
body_start_adrr += strlen("\r\n\r\n");
memcpy(p_rtsp_ctx->http.http_body_content,body_start_adrr,p_rtsp_ctx->http.content_len);
printf("body content is %s\n",p_rtsp_ctx->http.http_body_content);
}
return 0;
}
当返回状态是200的时候,此时rtsp client发送DESCRIBE给服务器,服务器进入下一个请求的响应
int rtsp_client_on_option_reply(rtsp_ctx *p_rtsp_ctx)
{
printf("p_rtsp_ctx->http.http_code %d\n",p_rtsp_ctx->http.http_code);
if(p_rtsp_ctx->http.http_code == 200){
//set next DESCRIBE,and send DESCRIBE
p_rtsp_ctx->e_state = RTSP_DESCRIBE;
return rtsp_client_send_describe(p_rtsp_ctx);
}else if(p_rtsp_ctx->http.http_code == 401){
//未认证,通过摘要进行认证然后重新下发数据
//TODO
}
return -1;
}