日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

网络摄像头Rtsp直播方案(二)

發(fā)布時(shí)間:2023/12/14 编程问答 22 豆豆
生活随笔 收集整理的這篇文章主要介紹了 网络摄像头Rtsp直播方案(二) 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

上一部分說(shuō)了Socket通訊的一些東西,這部分就結(jié)合代碼來(lái)說(shuō)說(shuō)RTSP交互的過程。
在放實(shí)現(xiàn)代碼之前先說(shuō)說(shuō)概念上的東西吧,關(guān)于RTSP這個(gè)流媒體網(wǎng)絡(luò)協(xié)議。RTSP作為一個(gè)應(yīng)用層協(xié)議,提供了一個(gè)可供擴(kuò)展的框架,使得流媒體的受控和點(diǎn)播變得可能,它主要用來(lái)控制具有實(shí)時(shí)特性的數(shù)據(jù)的發(fā)送,但其本身并不用于傳送流媒體數(shù)據(jù),而必須依賴下層傳輸協(xié)議(如RTP/RTCP)所提供的服務(wù)來(lái)完成流媒體數(shù)據(jù)的傳送。RTSP負(fù)責(zé)定義具體的控制信息、操作方法、狀態(tài)碼,以及描述與RTP之間的交互操作。所以具體的碼流數(shù)據(jù)其實(shí)是用RTP封裝傳輸?shù)?#xff0c;第三部分我會(huì)詳細(xì)講碼流數(shù)據(jù)的處理和發(fā)送。


一次基本的RTSP交互過程如上圖所示,C表示客戶端即請(qǐng)求碼流的用戶,S為服務(wù)器即網(wǎng)絡(luò)攝像機(jī)。
首先客戶端連接到流媒體服務(wù)器并發(fā)送一個(gè)RTSP描述請(qǐng)求(DESCRIBE request),服務(wù)器通過一個(gè)SDP(Session DescriptionProtocol)描述來(lái)進(jìn)行反饋(DESCRIBEresponse),反饋信息包括流數(shù)量、媒體類型等信息。客戶端分析該SDP描述,并為會(huì)話中的每一個(gè)流發(fā)送一個(gè)RTSP連接建立請(qǐng)求(SETUPrequest),該命令會(huì)告訴服務(wù)器用于接收媒體數(shù)據(jù)的端口,服務(wù)器響應(yīng)該請(qǐng)求(SETUP response)并建立連接之后,就開始傳送媒體流(RTP包)到客戶端。在播放過程中客戶端還可以向服務(wù)器發(fā)送請(qǐng)求來(lái)控制快進(jìn)、快退和暫停等。最后,客戶端可發(fā)送一個(gè)終止請(qǐng)求(TEARDOWN request)來(lái)結(jié)束流媒體會(huì)話。

RTSP最基本的東西就是這些,其他復(fù)雜的東西我也不想說(shuō)太多了,有興趣的可以查查RFC2326(假裝復(fù)雜的東西我懂似的),OK,講代碼。上一部分我放了我main函數(shù)寫的東西,在tcp_listen()這個(gè)函數(shù)之后我建立了一個(gè)Socket連接,并把套接字傳到了EventLoop()這個(gè)函數(shù)里面,上面說(shuō)了RTSP并不負(fù)責(zé)傳輸具體視音頻數(shù)據(jù),這部分是由RTP傳輸?shù)?#xff0c;所以在tcp_listen建立的套接字是用來(lái)做RTSP消息傳輸?shù)倪@里的SOCKET是TCP,后面我還會(huì)再建立新的UDP SOCKET用以傳輸具體的視頻數(shù)據(jù),這個(gè)具體后面會(huì)說(shuō)這里提一句。

static int s32ConCnt = 0;//已經(jīng)連接的客戶端數(shù)int s32Fd = -1;static RTSP_buffer *pRtspList=NULL;RTSP_buffer *p=NULL;unsigned int u32FdFound;/*接收連接,創(chuàng)建一個(gè)新的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)建一個(gè)連接,增加一個(gè)客戶端*/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);}}/*對(duì)已有的連接進(jìn)行調(diào)度*/ScheduleConnections(&pRtspList,&s32ConCnt);

上面是EventLoop()函數(shù)的源碼,講一下RTSP_buffer這個(gè)結(jié)構(gòu)體,定義在下面。因?yàn)檫@個(gè)直播是一對(duì)多的場(chǎng)景,即一個(gè)攝像頭,多個(gè)用戶同時(shí)觀看,所以注定連接數(shù)肯定是大于1的,那么,多一個(gè)連接,這個(gè)新的連接的套接字等信息肯定也不一樣,所以將每一個(gè)連接的屬性做一個(gè)統(tǒng)一的結(jié)構(gòu)體,且設(shè)置為鏈表的結(jié)構(gòu)便于處理。

typedef struct _RTSP_buffer {int fd; /*socket文件描述符*/unsigned int port;/*端口號(hào)*/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;/*序列號(hào)*/char descr[MAX_DESCR_LENGTH];/*描述*/RTSP_session *session_list;/*會(huì)話鏈表*/struct _RTSP_buffer *next; /*指向下一個(gè)結(jié)構(gòu)體,構(gòu)成了鏈表結(jié)構(gòu)*/ } RTSP_buffer;

OK,EventLoop()這個(gè)函數(shù)我們可以看出來(lái)首先是判斷是不是一個(gè)新的套接字:

  • 如果是一個(gè)新的套接字,給這個(gè)新的套接字建立一個(gè)新的連接,即加一個(gè)客戶端。進(jìn)到AddClient()函數(shù)
  • RTSP_buffer *pRtsp=NULL,*pRtspNew=NULL;#ifdef RTSP_DEBUGfprintf(stderr, "%s, %d\n", __FUNCTION__, __LINE__); #endif//在鏈表頭部插入第一個(gè)元素if (*ppRtspList==NULL){/*分配空間*/if ( !(*ppRtspList=(RTSP_buffer*)calloc(1,sizeof(RTSP_buffer)) ) ){fprintf(stderr,"alloc memory error %s,%i\n", __FILE__, __LINE__);return;}pRtsp = *ppRtspList;}else{//向鏈表中插入新的元素for (pRtsp=*ppRtspList; pRtsp!=NULL; pRtsp=pRtsp->next){pRtspNew=pRtsp;}/*在鏈表尾部插入*/if (pRtspNew!=NULL){if ( !(pRtspNew->next=(RTSP_buffer *)calloc(1,sizeof(RTSP_buffer)) ) ){fprintf(stderr, "error calloc %s,%i\n", __FILE__, __LINE__);return;}pRtsp=pRtspNew->next;pRtsp->next=NULL;}}//設(shè)置最大輪詢id號(hào)if(g_s32Maxfd < fd){g_s32Maxfd = fd;}/*初始化新添加的客戶端*/RTSP_initserver(pRtsp,fd);fprintf(stderr,"Incoming RTSP connection accepted on socket: %d\n",pRtsp->fd);

    從上面的AddClient()函數(shù)我們可以看到其實(shí)就是給新的連接的鏈表分配空間,初始化套接字,緩沖區(qū)等信息。

    2.如果不是一個(gè)新的套接字,即不是新的連接,進(jìn)到ScheduleConnections()進(jìn)行已有連接的交互操作

    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)關(guān)閉*/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 之前就截?cái)嗔诉B接,但是會(huì)話卻沒有被釋放*/if (pRtsp->session_list!=NULL){r=pRtsp->session_list->rtp_session;/*釋放所有會(huì)話*/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端口號(hào)放入到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){//鏈表第一個(gè)元素就出錯(cuò),則pRtspN為空printf("first error,pRtsp is null\n");*rtsp_list=pRtsp->next;free(pRtsp);pRtsp=*rtsp_list;}else{//不是鏈表中的第一個(gè),則把當(dāng)前出錯(cuò)任務(wù)刪除,并把next任務(wù)存放在pRtspN(上一個(gè)沒有出錯(cuò)的任務(wù))//指向的next,和當(dāng)前需要處理的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);}/*適當(dāng)情況下,釋放調(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{//沒有出錯(cuò)//上一個(gè)處理沒有出錯(cuò)的list存放在pRtspN中,需要處理的任務(wù)放在pRtst中pRtspN = pRtsp;pRtsp = pRtsp->next;}}

    上面的源碼主要是循環(huán)處理所有RTSP_buffer鏈表中的RTSP報(bào)文,具體處理過程在RtspServer()函數(shù)中,若其中某個(gè)連接有問題就會(huì)從鏈表中清除此連接并釋放相關(guān)的內(nèi)存。我們來(lá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 時(shí)間間隔*/t.tv_usec=100000;FD_SET(rtsp->fd,&rset);/*調(diào)用select等待對(duì)應(yīng)描述符變化*/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}/*有可供讀進(jìn)的rtsp包*/if (FD_ISSET(rtsp->fd,&rset)){memset(buffer,0,sizeof(buffer));size=sizeof(buffer)-1; /*最后一位用于填充字符串結(jié)束標(biāo)識(shí)*//*讀入數(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); //服務(wù)器內(nèi)部錯(cuò)誤消息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ù)溢出錯(cuò)誤}#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ù),進(jìn)行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在此出加入對(duì)RTCP數(shù)據(jù)的接收,并存放在緩存中。//繼而在schedule_do線程中對(duì)其處理。//rtcp控制處理,檢查讀入RTCP數(shù)據(jù)報(bào)return ERR_NOERROR;

    很熟悉吧?是我們上部分說(shuō)過的非阻塞的傳輸方式,通過Socket獲取客戶端發(fā)過來(lái)的rtsp信報(bào),然后將rtsp信報(bào)傳到RTSP_handler()函數(shù)進(jìn)行處理

    int s32Meth;while(pRtspBuf->in_size){s32Meth = RTSP_validate_method(pRtspBuf);if (s32Meth < 0){//錯(cuò)誤的請(qǐng)求,請(qǐng)求的方法不存在fprintf(stderr,"Bad Request %s,%d\n", __FILE__, __LINE__);printf("bad request, requestion not exit %d",s32Meth);send_reply(400, NULL, pRtspBuf);}else{//進(jìn)入到狀態(tài)機(jī),處理接收的請(qǐng)求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)這個(gè)函數(shù)是通過sscanf()來(lái)按格式讀取rtsp數(shù)據(jù)報(bào),這里簡(jiǎn)要分析一下這個(gè)函數(shù)的一些簡(jiǎn)單用法

  • 常見用法。
  • charstr[512]={0};
      sscanf(“123456”,"%s",str);
      printf(“str=%s”,str);

    2. 取指定長(zhǎng)度的字符串。如在下例中,取最大長(zhǎng)度為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()這個(gè)函數(shù)來(lái)讀取客戶端rtsp信報(bào)中當(dāng)前的方法來(lái)設(shè)置狀態(tài)并返回。
     這里重點(diǎn)講一下RTSP_state_machine()這個(gè)函數(shù),前面獲取的當(dāng)前方法會(huì)傳入這個(gè)函數(shù),這函數(shù)其實(shí)就是一個(gè)狀態(tài)機(jī),來(lái)實(shí)現(xiàn)各個(gè)方法的回傳的信報(bào)拼接并傳回客戶端。

    /*除了播放過程中發(fā)送的最后一個(gè)數(shù)據(jù)流,*所有的狀態(tài)遷移都在這里被處理* 狀態(tài)遷移位于stream_event中*/char *s;RTSP_session *pRtspSess;long int session_id;char trash[255];char szDebug[255];/*找到會(huì)話位置*/if ((s = strstr(pRtspBuf->in_buffer, HDR_SESSION)) != NULL){if (sscanf(s, "%254s %ld", trash, &session_id) != 2){fprintf(stderr,"Invalid Session number %s,%i\n", __FILE__, __LINE__);send_reply(454, 0, pRtspBuf); /* 沒有此會(huì)話*/return;}}/*打開會(huì)話列表*/pRtspSess = pRtspBuf->session_list;if (pRtspSess == NULL){return;}#ifdef RTSP_DEBUGsprintf(szDebug,"state_machine:current state is ");strcat(szDebug,((pRtspSess->cur_state==0)?"init state":((pRtspSess->cur_state==1)?"ready state":"play state")));printf("%s\n", szDebug); #endif/*根據(jù)狀態(tài)遷移規(guī)則,從當(dāng)前狀態(tài)開始遷移*/switch (pRtspSess->cur_state){case INIT_STATE: /*初始態(tài)*/{ #ifdef RTSP_DEBUGfprintf(stderr,"current method code is: %d \n",method); #endifswitch (method){case RTSP_ID_DESCRIBE: //狀態(tài)不變RTSP_describe(pRtspBuf);//printf("3\r\n");break;case RTSP_ID_SETUP: //狀態(tài)變?yōu)榫途w態(tài)//printf("4\r\n");if (RTSP_setup(pRtspBuf) == ERR_NOERROR){//printf("5\r\n");pRtspSess->cur_state = READY_STATE;fprintf(stderr,"TRANSFER TO READY STATE!\n");}break;case RTSP_ID_TEARDOWN: //狀態(tài)不變RTSP_teardown(pRtspBuf);break;case RTSP_ID_OPTIONS:if (RTSP_options(pRtspBuf) == ERR_NOERROR){pRtspSess->cur_state = INIT_STATE; //狀態(tài)不變}break;case RTSP_ID_PLAY: //method not valid this state.case RTSP_ID_PAUSE:send_reply(455, 0, pRtspBuf);break;default:send_reply(501, 0, pRtspBuf);break;}break;}case READY_STATE:{ #ifdef RTSP_DEBUGfprintf(stderr,"current method code is:%d\n",method); #endifswitch (method){case RTSP_ID_PLAY: //狀態(tài)遷移為播放態(tài)if (RTSP_play(pRtspBuf) == ERR_NOERROR){fprintf(stderr,"\tStart Playing!\n");pRtspSess->cur_state = PLAY_STATE;}break;case RTSP_ID_SETUP:if (RTSP_setup(pRtspBuf) == ERR_NOERROR) //狀態(tài)不變{pRtspSess->cur_state = READY_STATE;}break;case RTSP_ID_TEARDOWN:RTSP_teardown(pRtspBuf); //狀態(tài)變?yōu)槌跏紤B(tài) ?break;case RTSP_ID_OPTIONS:if (RTSP_options(pRtspBuf) == ERR_NOERROR){pRtspSess->cur_state = INIT_STATE; //狀態(tài)不變}break;case RTSP_ID_PAUSE: // method not valid this state.send_reply(455, 0, pRtspBuf);break;case RTSP_ID_DESCRIBE:RTSP_describe(pRtspBuf);break;default:send_reply(501, 0, pRtspBuf);break;}break;}case PLAY_STATE:{switch (method){case RTSP_ID_PLAY:// Feature not supportedfprintf(stderr,"UNSUPPORTED: Play while playing.\n");send_reply(551, 0, pRtspBuf); // Option not supportedbreak; /* //不支持暫停命令case RTSP_ID_PAUSE: //狀態(tài)變?yōu)榫途w態(tài)if (RTSP_pause(pRtspBuf) == ERR_NOERROR){pRtspSess->cur_state = READY_STATE;}break; */case RTSP_ID_TEARDOWN:RTSP_teardown(pRtspBuf); //狀態(tài)遷移為初始態(tài)break;case RTSP_ID_OPTIONS:break;case RTSP_ID_DESCRIBE:RTSP_describe(pRtspBuf);break;case RTSP_ID_SETUP:break;}break;}/* PLAY state */default:{/* invalid/unexpected current state. */fprintf(stderr,"%s State error: unknown state=%d, method code=%d\n", __FUNCTION__, pRtspSess->cur_state, method);}break;}/* end of current state switch */#ifdef RTSP_DEBUGprintf("leaving rtsp_state_machine!\n"); #endif

    以上就是處理rtsp交互的狀態(tài)機(jī),結(jié)合上面列出的RTSP交互過程,describe和setup兩個(gè)方法將在初始態(tài)處理,當(dāng)setup成功后,狀態(tài)變?yōu)榫途w態(tài),這個(gè)狀態(tài)下會(huì)給一些必要的屬性重新賦值,這些屬性是控制底層數(shù)據(jù)讀取的標(biāo)志,之后變成PLAY態(tài),這個(gè)狀態(tài)下會(huì)不斷從底層取數(shù)據(jù)處理并封裝發(fā)送到客戶端,一旦連接中斷,狀態(tài)又會(huì)變回初始態(tài)。下面我講一下各個(gè)方法的處理代碼。
    首先是Describe

    char object[255], trash[255];char *p;unsigned short port;char s8Url[255];char s8Descr[MAX_DESCR_LENGTH];char server[128];char s8Str[128];/*根據(jù)收到的請(qǐng)求請(qǐng)求消息,跳過方法名,分離出URL*/if (!sscanf(pRtsp->in_buffer, " %*s %254s ", s8Url))//%*s表示第一個(gè)匹配到的%s被過濾掉{fprintf(stderr, "Error %s,%i\n", __FILE__, __LINE__);send_reply(400, 0, pRtsp); /* bad request */printf("get URL error");return ERR_NOERROR;}/*驗(yàn)證URL */switch (ParseUrl(s8Url, server, &port, object, sizeof(object))){case 1: /*請(qǐng)求錯(cuò)誤*/fprintf(stderr, "Error %s,%i\n", __FILE__, __LINE__);send_reply(400, 0, pRtsp);printf("url request error");return ERR_NOERROR;break;case -1: /*內(nèi)部錯(cuò)誤*/fprintf(stderr,"url error while parsing !\n");send_reply(500, 0, pRtsp);printf("inner error");return ERR_NOERROR;break;default:break;}/*取得序列號(hào),并且必須有這個(gè)選項(xiàng)*/if ((p = strstr(pRtsp->in_buffer, HDR_CSEQ)) == NULL){fprintf(stderr, "Error %s,%i\n", __FILE__, __LINE__);send_reply(400, 0, pRtsp); /* Bad Request */printf("get serial num error");return ERR_NOERROR;}else{if (sscanf(p, "%254s %d", trash, &(pRtsp->rtsp_cseq)) != 2){fprintf(stderr, "Error %s,%i\n", __FILE__, __LINE__);send_reply(400, 0, pRtsp); /*請(qǐng)求錯(cuò)誤*/printf("get serial num 2 error");return ERR_NOERROR;}}//獲取SDP內(nèi)容GetSdpDescr(pRtsp, s8Descr, s8Str);//發(fā)送Describe響應(yīng)//printf("----------------1\r\n");SendDescribeReply(pRtsp, object, s8Descr, s8Str);//printf("2\r\n");return ERR_NOERROR;

    這里說(shuō)一下一些常用的字段處理函數(shù):
    ①strstr

    strstr(str1,str2)

    strstr()函數(shù)用于判斷字符串str2是否是str1的子串。如果是,則該函數(shù)返回str2在str1中首次出現(xiàn)的地址;否則,返回NULL。
    ② strncmp

    int strncmp ( const char * str1, const char * str2, size_t n );

    str1, str2 為需要比較的兩個(gè)字符串,n為要比較的字符的數(shù)目。若str1與str2的前n個(gè)字符相同,則返回0;若s1大于s2,則返回大于0的值;若s1 小于s2,則返回小于0的值。
    ③strchr

    char *strchr(const char *s,char c)

    查找字符串s中首次出現(xiàn)字符c的位置。
    ④strncpy

    char*strncpy(char *dest, const char *src, int n)

    把src所指向的字符串中以src地址開始的前n個(gè)字節(jié)復(fù)制到dest所指的數(shù)組中,并返回被復(fù)制后的dest
    ⑤strcpy

    char *strcpy(char* dest, const char *src);

    把從src地址開始且含有NULL結(jié)束符(’\0’)的字符串復(fù)制到以dest開始的地址空間,src和dest所指內(nèi)存區(qū)域不可以重疊且dest必須有足夠的空間來(lái)容納src的字符串。
    ⑥strcat

    char *strcat(char *dest, const char *src);

    把src所指向的字符串(包括“\0”)復(fù)制到dest所指向的字符串后面(刪除dest原來(lái)末尾的“\0”)。要保證dest足夠長(zhǎng),以容納被復(fù)制進(jìn)來(lái)的*src。*src中原有的字符不變。返回指向dest的指針。

    通過以上這些字段處理函數(shù)提取RTSP信報(bào)中想要的信息,并在GetSdpDescr(pRtsp, s8Descr, s8Str);這個(gè)函數(shù)中組建自己的SDP。這里貼一下組的SDP:

    struct ifreq stIfr;//linux 網(wǎng)絡(luò)接口用來(lái)配置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");//時(shí)間信息,分別表示開始的時(shí)間和結(jié)束的時(shí)間,一般在流媒體的直播的時(shí)移中見的比較多strcat(pDescr, "a=tool:Test Streaming Media v2018.11.30\r\n");//創(chuàng)建任務(wù)描述的工具的名稱及版本號(hào) strcat(pDescr, "a=type:broadcast\r\n");//會(huì)議類型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);

    關(guān)于SDP協(xié)議我這里不寫太多

    https://blog.csdn.net/jobbofhe/article/details/78477407

    這篇博文寫得挺全了我覺得,想了解多一點(diǎn)的可以在上面的網(wǎng)址看看。這里注意一下,sprop-parameter-sets=后面跟的是Base64編碼后的SPS和PPS。組好SDP之后發(fā)送出去

    char *pMsgBuf; /* 用于獲取響應(yīng)緩沖指針*/int s32MbLen;/* 分配空間,處理內(nèi)部錯(cuò)誤*/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;}/*構(gòu)造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); /*添加時(shí)間戳*/strcat(pMsgBuf, "Content-Type: application/sdp"RTSP_EL); /*實(shí)體頭,表示實(shí)體類型*//*用于解析實(shí)體內(nèi)相對(duì)url的 絕對(duì)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)); /*消息體的長(zhǎng)度*/strcat(pMsgBuf, RTSP_EL);/*消息頭結(jié)束*//*加上消息體*/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);//如果需要增加一個(gè)會(huì)話if ( !pRtsp->session_list ){pRtsp->session_list = (RTSP_session *) calloc(1, sizeof(RTSP_session));}rtsp_s = pRtsp->session_list;//建立一個(gè)新會(huì)話,插入到鏈表中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")){//如果指定了客戶端端口號(hào),填充對(duì)應(yīng)的兩個(gè)端口號(hào)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));}//服務(wù)器端口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));//如果有會(huì)話頭,就有了一個(gè)控制集合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)生一個(gè)非0的隨機(jī)的會(huì)話序號(hào)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;

    這里重點(diǎn)講一下兩地方吧,一個(gè)是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;}//初始化序號(hào)hRtp->u16SeqNum = 0;//初始化時(shí)間戳hRtp->u32TimeStampInc = 0;hRtp->u32TimeStampCurr = 0;//獲取當(dāng)前時(shí)間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;//獲取本機(jī)網(wǎng)絡(luò)設(shè)備名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);}

    這個(gè)函數(shù)主要是創(chuàng)建了一個(gè)用于RTP傳輸?shù)腟ocket,用來(lái)傳輸封裝好的RTP數(shù)據(jù)包,這一部分我會(huì)在第三部分講。另一個(gè)要注意的地方就是在

    pRtsp->session_list->rtp_session->sched_id = schedule_add(rtp_s);

    schedule_add(rtp_s)這個(gè)函數(shù)里面給每一個(gè)連接的關(guān)鍵參數(shù)置位,這些參數(shù)是用來(lái)控制取底層數(shù)據(jù)并保存在緩沖區(qū)的判斷依據(jù),還有就是設(shè)置了RtpSend()這個(gè)回調(diào)函數(shù),這個(gè)回調(diào)函數(shù)會(huì)在Play階段被調(diào)用,用來(lái)封裝底層上來(lái)的碼流數(shù)據(jù)并發(fā)送。

    Play和Teardown比較簡(jiǎn)單,我這邊就不放代碼了,PLay主要還是設(shè)置屬性,使進(jìn)入讀取底層數(shù)據(jù)并保存的判定為真,Teardown主要是釋放一些內(nèi)存,以及釋放RTSP鏈表的一些操作。

    總結(jié)一下吧,關(guān)于RTSP交互這一塊,首先就是建立一個(gè)Socket用以接受發(fā)送RTSP的數(shù)據(jù)報(bào)文的,通過這個(gè)Socket進(jìn)行我上面介紹過的服務(wù)器和客戶端的交互,在Setup階段創(chuàng)建新的Socket連接用來(lái)做具體的碼流數(shù)據(jù)傳輸,Play階段就是不斷的取底層數(shù)據(jù)進(jìn)行封裝發(fā)送,TearDown階段斷開連接并釋放相關(guān)的指針或鏈表。

    關(guān)于具體的碼流是怎么封裝的我會(huì)在第三部分講。

    總結(jié)

    以上是生活随笔為你收集整理的网络摄像头Rtsp直播方案(二)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。