【rtsp client取海康IPC H264视频流】----RTSP协议OPTIONS实现

        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;
}

猜你喜欢

转载自blog.csdn.net/weihan0208/article/details/125845564
今日推荐