网络摄像头Rtsp直播方案(二)
上一部分說了Socket通訊的一些東西,這部分就結合代碼來說說RTSP交互的過程。
在放實現(xiàn)代碼之前先說說概念上的東西吧,關于RTSP這個流媒體網(wǎng)絡協(xié)議。RTSP作為一個應用層協(xié)議,提供了一個可供擴展的框架,使得流媒體的受控和點播變得可能,它主要用來控制具有實時特性的數(shù)據(jù)的發(fā)送,但其本身并不用于傳送流媒體數(shù)據(jù),而必須依賴下層傳輸協(xié)議(如RTP/RTCP)所提供的服務來完成流媒體數(shù)據(jù)的傳送。RTSP負責定義具體的控制信息、操作方法、狀態(tài)碼,以及描述與RTP之間的交互操作。所以具體的碼流數(shù)據(jù)其實是用RTP封裝傳輸?shù)?#xff0c;第三部分我會詳細講碼流數(shù)據(jù)的處理和發(fā)送。
一次基本的RTSP交互過程如上圖所示,C表示客戶端即請求碼流的用戶,S為服務器即網(wǎng)絡攝像機。
首先客戶端連接到流媒體服務器并發(fā)送一個RTSP描述請求(DESCRIBE request),服務器通過一個SDP(Session DescriptionProtocol)描述來進行反饋(DESCRIBEresponse),反饋信息包括流數(shù)量、媒體類型等信息。客戶端分析該SDP描述,并為會話中的每一個流發(fā)送一個RTSP連接建立請求(SETUPrequest),該命令會告訴服務器用于接收媒體數(shù)據(jù)的端口,服務器響應該請求(SETUP response)并建立連接之后,就開始傳送媒體流(RTP包)到客戶端。在播放過程中客戶端還可以向服務器發(fā)送請求來控制快進、快退和暫停等。最后,客戶端可發(fā)送一個終止請求(TEARDOWN request)來結束流媒體會話。
RTSP最基本的東西就是這些,其他復雜的東西我也不想說太多了,有興趣的可以查查RFC2326(假裝復雜的東西我懂似的),OK,講代碼。上一部分我放了我main函數(shù)寫的東西,在tcp_listen()這個函數(shù)之后我建立了一個Socket連接,并把套接字傳到了EventLoop()這個函數(shù)里面,上面說了RTSP并不負責傳輸具體視音頻數(shù)據(jù),這部分是由RTP傳輸?shù)?#xff0c;所以在tcp_listen建立的套接字是用來做RTSP消息傳輸?shù)倪@里的SOCKET是TCP,后面我還會再建立新的UDP SOCKET用以傳輸具體的視頻數(shù)據(jù),這個具體后面會說這里提一句。
static int s32ConCnt = 0;//已經(jīng)連接的客戶端數(shù)int s32Fd = -1;static RTSP_buffer *pRtspList=NULL;RTSP_buffer *p=NULL;unsigned int u32FdFound;/*接收連接,創(chuàng)建一個新的socket*/if (s32ConCnt!=-1){s32Fd= tcp_accept(s32MainFd);}/*處理新創(chuàng)建的連接*/if (s32Fd >= 0){/*查找列表中是否存在此連接的socket*/for (u32FdFound=0,p=pRtspList; p!=NULL; p=p->next){if (p->fd == s32Fd){printf("### exit socket Fd ###\n");u32FdFound=1;break;}}if (!u32FdFound){/*創(chuàng)建一個連接,增加一個客戶端*/if (s32ConCnt<MAX_CONNECTION){++s32ConCnt;AddClient(&pRtspList,s32Fd);}else{fprintf(stderr, "exceed the MAX client, ignore this connecting\n");return;}num_conn++;fprintf(stderr, "%s Connection reached: %d\n", __FUNCTION__, num_conn);}}/*對已有的連接進行調(diào)度*/ScheduleConnections(&pRtspList,&s32ConCnt);上面是EventLoop()函數(shù)的源碼,講一下RTSP_buffer這個結構體,定義在下面。因為這個直播是一對多的場景,即一個攝像頭,多個用戶同時觀看,所以注定連接數(shù)肯定是大于1的,那么,多一個連接,這個新的連接的套接字等信息肯定也不一樣,所以將每一個連接的屬性做一個統(tǒng)一的結構體,且設置為鏈表的結構便于處理。
typedef struct _RTSP_buffer {int fd; /*socket文件描述符*/unsigned int port;/*端口號*/struct sockaddr stClientAddr;char in_buffer[RTSP_BUFFERSIZE];/*接收緩沖區(qū)*/unsigned int in_size;/*接收緩沖區(qū)的大小*/char out_buffer[RTSP_BUFFERSIZE+MAX_DESCR_LENGTH];/*發(fā)送緩沖區(qū)*/int out_size;/*發(fā)送緩沖區(qū)大小*/unsigned int rtsp_cseq;/*序列號*/char descr[MAX_DESCR_LENGTH];/*描述*/RTSP_session *session_list;/*會話鏈表*/struct _RTSP_buffer *next; /*指向下一個結構體,構成了鏈表結構*/ } RTSP_buffer;OK,EventLoop()這個函數(shù)我們可以看出來首先是判斷是不是一個新的套接字:
從上面的AddClient()函數(shù)我們可以看到其實就是給新的連接的鏈表分配空間,初始化套接字,緩沖區(qū)等信息。
2.如果不是一個新的套接字,即不是新的連接,進到ScheduleConnections()進行已有連接的交互操作
int res;RTSP_buffer *pRtsp=*rtsp_list,*pRtspN=NULL;RTP_session *r=NULL, *t=NULL;while (pRtsp!=NULL){if ((res = RtspServer(pRtsp))!=ERR_NOERROR){if (res==ERR_CONNECTION_CLOSE || res==ERR_GENERIC){/*連接已經(jīng)關閉*/if (res==ERR_CONNECTION_CLOSE)fprintf(stderr,"fd:%d,RTSP connection closed by client.\n",pRtsp->fd);elsefprintf(stderr,"fd:%d,RTSP connection closed by server.\n",pRtsp->fd);/*客戶端在發(fā)送TEARDOWN 之前就截斷了連接,但是會話卻沒有被釋放*/if (pRtsp->session_list!=NULL){r=pRtsp->session_list->rtp_session;/*釋放所有會話*/while (r!=NULL){t = r->next;RtpDelete((unsigned int)(r->hndRtp));schedule_remove(r->sched_id);r=t;}/*釋放鏈表頭指針*/free(pRtsp->session_list);pRtsp->session_list=NULL;g_s32DoPlay--;if (g_s32DoPlay == 0) {printf("user abort! no user online now resetfifo\n");ringreset();/* 重新將所有可用的RTP端口號放入到port_pool[MAX_SESSION] 中 */RTP_port_pool_init(RTP_DEFAULT_PORT);}fprintf(stderr,"WARNING! fd:%d RTSP connection truncated before ending operations.\n",pRtsp->fd);}// wait forclose(pRtsp->fd);--*conn_count;num_conn--;/*釋放rtsp緩沖區(qū)*/if (pRtsp==*rtsp_list){//鏈表第一個元素就出錯,則pRtspN為空printf("first error,pRtsp is null\n");*rtsp_list=pRtsp->next;free(pRtsp);pRtsp=*rtsp_list;}else{//不是鏈表中的第一個,則把當前出錯任務刪除,并把next任務存放在pRtspN(上一個沒有出錯的任務)//指向的next,和當前需要處理的pRtsp中.printf("dell current fd:%d\n",pRtsp->fd);pRtspN->next=pRtsp->next;free(pRtsp);pRtsp=pRtspN->next;printf("current next fd:%d\n",pRtsp->fd);}/*適當情況下,釋放調(diào)度器本身*/if (pRtsp==NULL && *conn_count<0){fprintf(stderr,"to stop cchedule_do thread\n");stop_schedule=1;}}else{ printf("current fd:%d\n",pRtsp->fd);pRtsp = pRtsp->next;}}else{//沒有出錯//上一個處理沒有出錯的list存放在pRtspN中,需要處理的任務放在pRtst中pRtspN = pRtsp;pRtsp = pRtsp->next;}}上面的源碼主要是循環(huán)處理所有RTSP_buffer鏈表中的RTSP報文,具體處理過程在RtspServer()函數(shù)中,若其中某個連接有問題就會從鏈表中清除此連接并釋放相關的內(nèi)存。我們來看看RtspServer():
fd_set rset,wset; /*讀寫I/O描述集*/struct timeval t;int size;static char buffer[RTSP_BUFFERSIZE+1]; /* +1 to control the final '\0'*/int n;int res;struct sockaddr ClientAddr;if (rtsp == NULL){return ERR_NOERROR;}/*變量初始化*/FD_ZERO(&rset);FD_ZERO(&wset);t.tv_sec=0; /*select 時間間隔*/t.tv_usec=100000;FD_SET(rtsp->fd,&rset);/*調(diào)用select等待對應描述符變化*/if (select(g_s32Maxfd+1,&rset,0,0,&t)<0){fprintf(stderr,"select error %s %d\n", __FILE__, __LINE__);send_reply(500, NULL, rtsp);return ERR_GENERIC; //errore interno al server}/*有可供讀進的rtsp包*/if (FD_ISSET(rtsp->fd,&rset)){memset(buffer,0,sizeof(buffer));size=sizeof(buffer)-1; /*最后一位用于填充字符串結束標識*//*讀入數(shù)據(jù)到緩沖區(qū)中*/ #ifdef RTSP_DEBUGfprintf(stderr, "tcp_read, %d\n", __LINE__); #endifn= tcp_read(rtsp->fd, buffer, size, &ClientAddr);if (n==0){return ERR_CONNECTION_CLOSE;}if (n<0){fprintf(stderr,"read() error %s %d\n", __FILE__, __LINE__);send_reply(500, NULL, rtsp); //服務器內(nèi)部錯誤消息return ERR_GENERIC;}//檢查讀入的數(shù)據(jù)是否產(chǎn)生溢出if (rtsp->in_size+n>RTSP_BUFFERSIZE){fprintf(stderr,"RTSP buffer overflow (input RTSP message is most likely invalid).\n");send_reply(500, NULL, rtsp);return ERR_GENERIC;//數(shù)據(jù)溢出錯誤}#ifdef RTSP_DEBUGfprintf(stderr,"INPUT_BUFFER was:%s\n", buffer); #endif/*填充數(shù)據(jù)*/memcpy(&(rtsp->in_buffer[rtsp->in_size]),buffer,n);rtsp->in_size+=n;//清空buffermemset(buffer, 0, n);//添加客戶端地址信息memcpy( &rtsp->stClientAddr, &ClientAddr, sizeof(ClientAddr));/*處理緩沖區(qū)的數(shù)據(jù),進行rtsp處理*/if ((res=RTSP_handler(rtsp))==ERR_GENERIC){fprintf(stderr,"Invalid input message.\n");return ERR_NOERROR;}}/*有發(fā)送數(shù)據(jù)*/if (rtsp->out_size>0){//將數(shù)據(jù)發(fā)送出去n= tcp_write(rtsp->fd,rtsp->out_buffer,rtsp->out_size); fprintf(stderr,"S");fflush(stderr);if (n<0){fprintf(stderr,"tcp_write error %s %i\n", __FILE__, __LINE__);send_reply(500, NULL, rtsp);return ERR_GENERIC; //errore interno al server}#ifdef RTSP_DEBUGfprintf(stderr,"OUTPUT_BUFFER length %d\n%s\n", rtsp->out_size, rtsp->out_buffer); #endif//清空發(fā)送緩沖區(qū)memset(rtsp->out_buffer, 0, rtsp->out_size);rtsp->out_size = 0;}//如果需要RTCP在此出加入對RTCP數(shù)據(jù)的接收,并存放在緩存中。//繼而在schedule_do線程中對其處理。//rtcp控制處理,檢查讀入RTCP數(shù)據(jù)報return ERR_NOERROR;很熟悉吧?是我們上部分說過的非阻塞的傳輸方式,通過Socket獲取客戶端發(fā)過來的rtsp信報,然后將rtsp信報傳到RTSP_handler()函數(shù)進行處理
int s32Meth;while(pRtspBuf->in_size){s32Meth = RTSP_validate_method(pRtspBuf);if (s32Meth < 0){//錯誤的請求,請求的方法不存在fprintf(stderr,"Bad Request %s,%d\n", __FILE__, __LINE__);printf("bad request, requestion not exit %d",s32Meth);send_reply(400, NULL, pRtspBuf);}else{//進入到狀態(tài)機,處理接收的請求RTSP_state_machine(pRtspBuf, s32Meth);printf("exit Rtsp_state_machine\r\n");}//丟棄處理之后的消息RTSP_discard_msg(pRtspBuf);printf(" After RTSP_discard_msg\r\n");}return ERR_NOERROR;RTSP_validate_method(pRtspBuf)這個函數(shù)是通過sscanf()來按格式讀取rtsp數(shù)據(jù)報,這里簡要分析一下這個函數(shù)的一些簡單用法
charstr[512]={0};
sscanf(“123456”,"%s",str);
printf(“str=%s”,str);
2. 取指定長度的字符串。如在下例中,取最大長度為4字節(jié)的字符串。
sscanf(“123456”,"%4s",str);
printf(“str=%s”,str);
3. 取到指定字符為止的字符串。如在下例中,取遇到空格為止字符串。
sscanf(“123456abcdedf”,"%[^]",str);
printf(“str=%s”,str);
4. 取僅包含指定字符集的字符串。如在下例中,取僅包含1到9和小寫字母的字符串。
sscanf(“123456abcdedfBCDEF”,"%[1-9a-z]",str);
printf(“str=%s”,str);
5. 取到指定字符集為止的字符串。如在下例中,取遇到大寫字母為止的字符串。
sscanf(“123456abcdedfBCDEF”,"%[^A-Z]",str);
printf(“str=%s”,str);
RTSP_validate_method()通過sscanf()這個函數(shù)來讀取客戶端rtsp信報中當前的方法來設置狀態(tài)并返回。
這里重點講一下RTSP_state_machine()這個函數(shù),前面獲取的當前方法會傳入這個函數(shù),這函數(shù)其實就是一個狀態(tài)機,來實現(xiàn)各個方法的回傳的信報拼接并傳回客戶端。
以上就是處理rtsp交互的狀態(tài)機,結合上面列出的RTSP交互過程,describe和setup兩個方法將在初始態(tài)處理,當setup成功后,狀態(tài)變?yōu)榫途w態(tài),這個狀態(tài)下會給一些必要的屬性重新賦值,這些屬性是控制底層數(shù)據(jù)讀取的標志,之后變成PLAY態(tài),這個狀態(tài)下會不斷從底層取數(shù)據(jù)處理并封裝發(fā)送到客戶端,一旦連接中斷,狀態(tài)又會變回初始態(tài)。下面我講一下各個方法的處理代碼。
首先是Describe
這里說一下一些常用的字段處理函數(shù):
①strstr
strstr()函數(shù)用于判斷字符串str2是否是str1的子串。如果是,則該函數(shù)返回str2在str1中首次出現(xiàn)的地址;否則,返回NULL。
② strncmp
str1, str2 為需要比較的兩個字符串,n為要比較的字符的數(shù)目。若str1與str2的前n個字符相同,則返回0;若s1大于s2,則返回大于0的值;若s1 小于s2,則返回小于0的值。
③strchr
查找字符串s中首次出現(xiàn)字符c的位置。
④strncpy
把src所指向的字符串中以src地址開始的前n個字節(jié)復制到dest所指的數(shù)組中,并返回被復制后的dest
⑤strcpy
把從src地址開始且含有NULL結束符(’\0’)的字符串復制到以dest開始的地址空間,src和dest所指內(nèi)存區(qū)域不可以重疊且dest必須有足夠的空間來容納src的字符串。
⑥strcat
把src所指向的字符串(包括“\0”)復制到dest所指向的字符串后面(刪除dest原來末尾的“\0”)。要保證dest足夠長,以容納被復制進來的*src。*src中原有的字符不變。返回指向dest的指針。
通過以上這些字段處理函數(shù)提取RTSP信報中想要的信息,并在GetSdpDescr(pRtsp, s8Descr, s8Str);這個函數(shù)中組建自己的SDP。這里貼一下組的SDP:
struct ifreq stIfr;//linux 網(wǎng)絡接口用來配置ip地址,激活接口,配置MTUchar pSdpId[128];//char rtp_port[5];strcpy(stIfr.ifr_name, "br0");if(ioctl(pRtsp->fd, SIOCGIFADDR, &stIfr) < 0){//printf("Failed to get host eth0 ip\n");strcpy(stIfr.ifr_name, "eth0");if(ioctl(pRtsp->fd, SIOCGIFADDR, &stIfr) < 0){printf("Failed to get host br0 or eth0 ip\n");}}sock_ntop_host(&stIfr.ifr_addr, sizeof(struct sockaddr), s8Str, 128);GetSdpId(pSdpId);strcpy(pDescr, "v=0\r\n"); strcat(pDescr, "o=-");strcat(pDescr, pSdpId);strcat(pDescr," ");strcat(pDescr, pSdpId);strcat(pDescr," IN IP4 ");strcat(pDescr, s8Str);strcat(pDescr, "\r\n");strcat(pDescr, "s=H.264 Video, streamed by the Test Media Server\r\n");//(session name)strcat(pDescr, "i=test.h264\r\n");//session的信息strcat(pDescr, "t=0 0\r\n");//時間信息,分別表示開始的時間和結束的時間,一般在流媒體的直播的時移中見的比較多strcat(pDescr, "a=tool:Test Streaming Media v2018.11.30\r\n");//創(chuàng)建任務描述的工具的名稱及版本號 strcat(pDescr, "a=type:broadcast\r\n");//會議類型strcat(pDescr, "a=control:*\r\n");strcat(pDescr, "m=video 0 RTP/AVP 96\r\n");strcat(pDescr, "\r\n"); strcat(pDescr,"b=AS:500\r\n");/**** Dynamically defined payload ****/strcat(pDescr,"a=rtpmap:96");strcat(pDescr," "); strcat(pDescr,"H264/90000");strcat(pDescr, "\r\n");strcat(pDescr,"a=fmtp:96 packetization-mode=1;");strcat(pDescr,"profile-level-id=");strcat(pDescr,psp.base64profileid);strcat(pDescr,";sprop-parameter-sets=");strcat(pDescr,psp.base64sps);strcat(pDescr,",");strcat(pDescr,psp.base64pps);strcat(pDescr, "\r\n");strcat(pDescr,"a=control:track1");strcat(pDescr, "\r\n");printf("\n\n%s,%d===>psp.base64profileid=%s,psp.base64sps=%s,psp.base64pps=%s\n\n",__FUNCTION__,__LINE__,psp.base64profileid,psp.base64sps,psp.base64pps);關于SDP協(xié)議我這里不寫太多
https://blog.csdn.net/jobbofhe/article/details/78477407
這篇博文寫得挺全了我覺得,想了解多一點的可以在上面的網(wǎng)址看看。這里注意一下,sprop-parameter-sets=后面跟的是Base64編碼后的SPS和PPS。組好SDP之后發(fā)送出去
char *pMsgBuf; /* 用于獲取響應緩沖指針*/int s32MbLen;/* 分配空間,處理內(nèi)部錯誤*/s32MbLen = 2048;pMsgBuf = (char *)malloc(s32MbLen);if (!pMsgBuf){fprintf(stderr,"send_describe_reply(): unable to allocate memory\n");send_reply(500, 0, rtsp); /* internal server error */if (pMsgBuf){free(pMsgBuf);}return ERR_ALLOC;}/*構造describe消息串*/sprintf(pMsgBuf, "%s %d %s"RTSP_EL"CSeq: %d"RTSP_EL"Server: %s/%s"RTSP_EL, RTSP_VER, 200, get_stat(200), rtsp->rtsp_cseq, PACKAGE, VERSION);add_time_stamp(pMsgBuf, 0); /*添加時間戳*/strcat(pMsgBuf, "Content-Type: application/sdp"RTSP_EL); /*實體頭,表示實體類型*//*用于解析實體內(nèi)相對url的 絕對url*/sprintf(pMsgBuf + strlen(pMsgBuf), "Content-Base: rtsp://%s/%s/"RTSP_EL, s8Str, object);sprintf(pMsgBuf + strlen(pMsgBuf), "Content-Length: %d"RTSP_EL, strlen(descr)); /*消息體的長度*/strcat(pMsgBuf, RTSP_EL);/*消息頭結束*//*加上消息體*/strcat(pMsgBuf, descr); /*describe消息*//*向緩沖區(qū)中填充數(shù)據(jù)*/bwrite(pMsgBuf, (unsigned short) strlen(pMsgBuf), rtsp);free(pMsgBuf);return ERR_NOERROR;Describe 之后是SetUp
char s8TranStr[128], *s8Str;char *pStr;RTP_transport Transport;int s32SessionID=0;RTP_session *rtp_s, *rtp_s_prec;RTSP_session *rtsp_s;if ((s8Str = strstr(pRtsp->in_buffer, HDR_TRANSPORT)) == NULL){fprintf(stderr, "Error %s,%i\n", __FILE__, __LINE__);send_reply(406, 0, pRtsp); // Not Acceptableprintf("not acceptable");return ERR_NOERROR;}//檢查傳輸層子串是否正確if (sscanf(s8Str, "%*10s %255s", s8TranStr) != 1){fprintf(stderr,"SETUP request malformed: Transport string is empty\n");send_reply(400, 0, pRtsp); // Bad Requestprintf("check transport 400 bad request");return ERR_NOERROR;}fprintf(stderr,"*** transport: %s ***\n", s8TranStr);//如果需要增加一個會話if ( !pRtsp->session_list ){pRtsp->session_list = (RTSP_session *) calloc(1, sizeof(RTSP_session));}rtsp_s = pRtsp->session_list;//建立一個新會話,插入到鏈表中if (pRtsp->session_list->rtp_session == NULL){pRtsp->session_list->rtp_session = (RTP_session *) calloc(1, sizeof(RTP_session));rtp_s = pRtsp->session_list->rtp_session;}else{for (rtp_s = rtsp_s->rtp_session; rtp_s != NULL; rtp_s = rtp_s->next){rtp_s_prec = rtp_s;}rtp_s_prec->next = (RTP_session *) calloc(1, sizeof(RTP_session));rtp_s = rtp_s_prec->next;}//起始狀態(tài)為暫停rtp_s->pause = 1;rtp_s->hndRtp = NULL;Transport.type = RTP_no_transport;if((pStr = strstr(s8TranStr, RTSP_RTP_AVP))){//Transport: RTP/AVPpStr += strlen(RTSP_RTP_AVP);if ( !*pStr || (*pStr == ';') || (*pStr == ' ')){//單播if (strstr(s8TranStr, "unicast")){//如果指定了客戶端端口號,填充對應的兩個端口號if( (pStr = strstr(s8TranStr, "client_port")) ){pStr = strstr(s8TranStr, "=");sscanf(pStr + 1, "%d", &(Transport.u.udp.cli_ports.RTP));pStr = strstr(s8TranStr, "-");sscanf(pStr + 1, "%d", &(Transport.u.udp.cli_ports.RTCP));}//服務器端口if (RTP_get_port_pair(&Transport.u.udp.ser_ports) != ERR_NOERROR){fprintf(stderr, "Error %s,%d\n", __FILE__, __LINE__);send_reply(500, 0, pRtsp);/* Internal server error */return ERR_GENERIC;}//建立RTP套接字rtp_s->hndRtp = (struct _tagStRtpHandle*)RtpCreate((unsigned int)(((struct sockaddr_in *)(&pRtsp->stClientAddr))->sin_addr.s_addr), Transport.u.udp.cli_ports.RTP, _h264nalu);printf("<><><><>Creat RTP<><><><>\n");Transport.u.udp.is_multicast = 0;}else{printf("multicast not codeing\n");//multicast 多播處理....}Transport.type = RTP_rtp_avp;}else if (!strncmp(s8TranStr, "/TCP", 4)){if( (pStr = strstr(s8TranStr, "interleaved")) ){pStr = strstr(s8TranStr, "=");sscanf(pStr + 1, "%d", &(Transport.u.tcp.interleaved.RTP));if ((pStr = strstr(pStr, "-")))sscanf(pStr + 1, "%d", &(Transport.u.tcp.interleaved.RTCP));elseTransport.u.tcp.interleaved.RTCP = Transport.u.tcp.interleaved.RTP + 1;}else{}Transport.rtp_fd = pRtsp->fd; // Transport.rtcp_fd_out = pRtsp->fd; // Transport.rtcp_fd_in = -1;}}printf("pstr=%s\n",pStr);if (Transport.type == RTP_no_transport){fprintf(stderr,"AAAAAAAAAAA Unsupported Transport,%s,%d\n", __FILE__, __LINE__);send_reply(461, 0, pRtsp);// Bad Requestreturn ERR_NOERROR;}memcpy(&rtp_s->transport, &Transport, sizeof(Transport));//如果有會話頭,就有了一個控制集合if ((pStr = strstr(pRtsp->in_buffer, HDR_SESSION)) != NULL){if (sscanf(pStr, "%*s %d", &s32SessionID) != 1){fprintf(stderr, "Error %s,%i\n", __FILE__, __LINE__);send_reply(454, 0, pRtsp); // Session Not Foundreturn ERR_NOERROR;}}else{//產(chǎn)生一個非0的隨機的會話序號struct timeval stNowTmp;gettimeofday(&stNowTmp, 0);srand((stNowTmp.tv_sec * 1000) + (stNowTmp.tv_usec / 1000));s32SessionID = 1 + (int) (10.0 * rand() / (100000 + 1.0));if (s32SessionID == 0){s32SessionID++;}}pRtsp->session_list->session_id = s32SessionID;pRtsp->session_list->rtp_session->sched_id = schedule_add(rtp_s);send_setup_reply(pRtsp, rtsp_s, rtp_s);return ERR_NOERROR;這里重點講一下兩地方吧,一個是RtpCreate()函數(shù)
HndRtp hRtp = NULL;struct timeval stTimeval;struct ifreq stIfr;int s32Broadcast = 1;struct sockaddr_in addr;hRtp = (HndRtp)calloc(1, sizeof(StRtpObj));if(NULL == hRtp){printf("Failed to create RTP handle\n");goto cleanup;}hRtp->s32Sock = -1;if((hRtp->s32Sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0){printf("Failed to create socket\n");goto cleanup;}if(0xFF000000 == (u32IP & 0xFF000000)){if(-1 == setsockopt(hRtp->s32Sock, SOL_SOCKET, SO_BROADCAST, (char *)&s32Broadcast, sizeof(s32Broadcast))){printf("Failed to set socket\n");goto cleanup;}}memset(&addr, 0, sizeof(addr));while(1){addr.sin_port = BigLittleSwap16(server_port);addr.sin_family = AF_INET;addr.sin_addr.s_addr = INADDR_ANY;hRtp->stServAddr.sin_family = AF_INET;hRtp->stServAddr.sin_port = BigLittleSwap16(s32Port);hRtp->stServAddr.sin_addr.s_addr = u32IP;bzero(&(hRtp->stServAddr.sin_zero), 8);if (bind(hRtp->s32Sock, (struct sockaddr *)&addr, sizeof(addr))){printf("can't bind !!!!!!!!!!!!!!!!!!!!!!!!!!!");server_port++;}elsebreak;}//初始化序號hRtp->u16SeqNum = 0;//初始化時間戳hRtp->u32TimeStampInc = 0;hRtp->u32TimeStampCurr = 0;//獲取當前時間if(gettimeofday(&stTimeval, NULL) == -1){printf("Failed to get os time\n");goto cleanup;}hRtp->u32PrevTime = stTimeval.tv_sec * 1000 + stTimeval.tv_usec / 1000;hRtp->emPayload = emPayload;//獲取本機網(wǎng)絡設備名strcpy(stIfr.ifr_name, "br0");if(ioctl(hRtp->s32Sock, SIOCGIFADDR, &stIfr) < 0){//printf("Failed to get host ip\n");strcpy(stIfr.ifr_name, "eth0");if(ioctl(hRtp->s32Sock, SIOCGIFADDR, &stIfr) < 0){printf("Failed to get host eth0 or wlan0 ip\n");goto cleanup;}}hRtp->u32SSrc = BigLittleSwap32(((struct sockaddr_in *)(&stIfr.ifr_addr))->sin_addr.s_addr);local_ip = hRtp->u32SSrc;//hRtp->u32SSrc = htonl(((struct sockaddr_in *)(&stIfr.ifr_addr))->sin_addr.s_addr);printf("!!!!!!!!!!!!!!!!!!!!!!rtp create:addr:%x,port:%d,local%x\n",u32IP,s32Port,hRtp->u32SSrc);printf("<><><><>success creat RTP<><><><>\n");return (unsigned int)hRtp; cleanup:if(hRtp){if(hRtp->s32Sock >= 0){close(hRtp->s32Sock);}free(hRtp);}這個函數(shù)主要是創(chuàng)建了一個用于RTP傳輸?shù)腟ocket,用來傳輸封裝好的RTP數(shù)據(jù)包,這一部分我會在第三部分講。另一個要注意的地方就是在
pRtsp->session_list->rtp_session->sched_id = schedule_add(rtp_s);schedule_add(rtp_s)這個函數(shù)里面給每一個連接的關鍵參數(shù)置位,這些參數(shù)是用來控制取底層數(shù)據(jù)并保存在緩沖區(qū)的判斷依據(jù),還有就是設置了RtpSend()這個回調(diào)函數(shù),這個回調(diào)函數(shù)會在Play階段被調(diào)用,用來封裝底層上來的碼流數(shù)據(jù)并發(fā)送。
Play和Teardown比較簡單,我這邊就不放代碼了,PLay主要還是設置屬性,使進入讀取底層數(shù)據(jù)并保存的判定為真,Teardown主要是釋放一些內(nèi)存,以及釋放RTSP鏈表的一些操作。
總結一下吧,關于RTSP交互這一塊,首先就是建立一個Socket用以接受發(fā)送RTSP的數(shù)據(jù)報文的,通過這個Socket進行我上面介紹過的服務器和客戶端的交互,在Setup階段創(chuàng)建新的Socket連接用來做具體的碼流數(shù)據(jù)傳輸,Play階段就是不斷的取底層數(shù)據(jù)進行封裝發(fā)送,TearDown階段斷開連接并釋放相關的指針或鏈表。
關于具體的碼流是怎么封裝的我會在第三部分講。
總結
以上是生活随笔為你收集整理的网络摄像头Rtsp直播方案(二)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: OSChina 娱乐弹弹弹——假期就是睡
- 下一篇: 浏览此博客者开年必时来运转,否极泰来