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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

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

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

上一部分說了Socket通訊的一些東西,這部分就結合代碼來說說RTSP交互的過程。
在放實現代碼之前先說說概念上的東西吧,關于RTSP這個流媒體網絡協議。RTSP作為一個應用層協議,提供了一個可供擴展的框架,使得流媒體的受控和點播變得可能,它主要用來控制具有實時特性的數據的發送,但其本身并不用于傳送流媒體數據,而必須依賴下層傳輸協議(如RTP/RTCP)所提供的服務來完成流媒體數據的傳送。RTSP負責定義具體的控制信息、操作方法、狀態碼,以及描述與RTP之間的交互操作。所以具體的碼流數據其實是用RTP封裝傳輸的,第三部分我會詳細講碼流數據的處理和發送。


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

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

static int s32ConCnt = 0;//已經連接的客戶端數int s32Fd = -1;static RTSP_buffer *pRtspList=NULL;RTSP_buffer *p=NULL;unsigned int u32FdFound;/*接收連接,創建一個新的socket*/if (s32ConCnt!=-1){s32Fd= tcp_accept(s32MainFd);}/*處理新創建的連接*/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){/*創建一個連接,增加一個客戶端*/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);}}/*對已有的連接進行調度*/ScheduleConnections(&pRtspList,&s32ConCnt);

上面是EventLoop()函數的源碼,講一下RTSP_buffer這個結構體,定義在下面。因為這個直播是一對多的場景,即一個攝像頭,多個用戶同時觀看,所以注定連接數肯定是大于1的,那么,多一個連接,這個新的連接的套接字等信息肯定也不一樣,所以將每一個連接的屬性做一個統一的結構體,且設置為鏈表的結構便于處理。

typedef struct _RTSP_buffer {int fd; /*socket文件描述符*/unsigned int port;/*端口號*/struct sockaddr stClientAddr;char in_buffer[RTSP_BUFFERSIZE];/*接收緩沖區*/unsigned int in_size;/*接收緩沖區的大小*/char out_buffer[RTSP_BUFFERSIZE+MAX_DESCR_LENGTH];/*發送緩沖區*/int out_size;/*發送緩沖區大小*/unsigned int rtsp_cseq;/*序列號*/char descr[MAX_DESCR_LENGTH];/*描述*/RTSP_session *session_list;/*會話鏈表*/struct _RTSP_buffer *next; /*指向下一個結構體,構成了鏈表結構*/ } RTSP_buffer;

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

  • 如果是一個新的套接字,給這個新的套接字建立一個新的連接,即加一個客戶端。進到AddClient()函數
  • RTSP_buffer *pRtsp=NULL,*pRtspNew=NULL;#ifdef RTSP_DEBUGfprintf(stderr, "%s, %d\n", __FUNCTION__, __LINE__); #endif//在鏈表頭部插入第一個元素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;}}//設置最大輪詢id號if(g_s32Maxfd < fd){g_s32Maxfd = fd;}/*初始化新添加的客戶端*/RTSP_initserver(pRtsp,fd);fprintf(stderr,"Incoming RTSP connection accepted on socket: %d\n",pRtsp->fd);

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

    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){/*連接已經關閉*/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);/*客戶端在發送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緩沖區*/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);}/*適當情況下,釋放調度器本身*/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;}}

    上面的源碼主要是循環處理所有RTSP_buffer鏈表中的RTSP報文,具體處理過程在RtspServer()函數中,若其中某個連接有問題就會從鏈表中清除此連接并釋放相關的內存。我們來看看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);/*調用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; /*最后一位用于填充字符串結束標識*//*讀入數據到緩沖區中*/ #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); //服務器內部錯誤消息return ERR_GENERIC;}//檢查讀入的數據是否產生溢出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;//數據溢出錯誤}#ifdef RTSP_DEBUGfprintf(stderr,"INPUT_BUFFER was:%s\n", buffer); #endif/*填充數據*/memcpy(&(rtsp->in_buffer[rtsp->in_size]),buffer,n);rtsp->in_size+=n;//清空buffermemset(buffer, 0, n);//添加客戶端地址信息memcpy( &rtsp->stClientAddr, &ClientAddr, sizeof(ClientAddr));/*處理緩沖區的數據,進行rtsp處理*/if ((res=RTSP_handler(rtsp))==ERR_GENERIC){fprintf(stderr,"Invalid input message.\n");return ERR_NOERROR;}}/*有發送數據*/if (rtsp->out_size>0){//將數據發送出去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//清空發送緩沖區memset(rtsp->out_buffer, 0, rtsp->out_size);rtsp->out_size = 0;}//如果需要RTCP在此出加入對RTCP數據的接收,并存放在緩存中。//繼而在schedule_do線程中對其處理。//rtcp控制處理,檢查讀入RTCP數據報return ERR_NOERROR;

    很熟悉吧?是我們上部分說過的非阻塞的傳輸方式,通過Socket獲取客戶端發過來的rtsp信報,然后將rtsp信報傳到RTSP_handler()函數進行處理

    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{//進入到狀態機,處理接收的請求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)這個函數是通過sscanf()來按格式讀取rtsp數據報,這里簡要分析一下這個函數的一些簡單用法

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

    2. 取指定長度的字符串。如在下例中,取最大長度為4字節的字符串。

    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()這個函數來讀取客戶端rtsp信報中當前的方法來設置狀態并返回。
     這里重點講一下RTSP_state_machine()這個函數,前面獲取的當前方法會傳入這個函數,這函數其實就是一個狀態機,來實現各個方法的回傳的信報拼接并傳回客戶端。

    /*除了播放過程中發送的最后一個數據流,*所有的狀態遷移都在這里被處理* 狀態遷移位于stream_event中*/char *s;RTSP_session *pRtspSess;long int session_id;char trash[255];char szDebug[255];/*找到會話位置*/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); /* 沒有此會話*/return;}}/*打開會話列表*/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/*根據狀態遷移規則,從當前狀態開始遷移*/switch (pRtspSess->cur_state){case INIT_STATE: /*初始態*/{ #ifdef RTSP_DEBUGfprintf(stderr,"current method code is: %d \n",method); #endifswitch (method){case RTSP_ID_DESCRIBE: //狀態不變RTSP_describe(pRtspBuf);//printf("3\r\n");break;case RTSP_ID_SETUP: //狀態變為就緒態//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: //狀態不變RTSP_teardown(pRtspBuf);break;case RTSP_ID_OPTIONS:if (RTSP_options(pRtspBuf) == ERR_NOERROR){pRtspSess->cur_state = INIT_STATE; //狀態不變}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: //狀態遷移為播放態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) //狀態不變{pRtspSess->cur_state = READY_STATE;}break;case RTSP_ID_TEARDOWN:RTSP_teardown(pRtspBuf); //狀態變為初始態 ?break;case RTSP_ID_OPTIONS:if (RTSP_options(pRtspBuf) == ERR_NOERROR){pRtspSess->cur_state = INIT_STATE; //狀態不變}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: //狀態變為就緒態if (RTSP_pause(pRtspBuf) == ERR_NOERROR){pRtspSess->cur_state = READY_STATE;}break; */case RTSP_ID_TEARDOWN:RTSP_teardown(pRtspBuf); //狀態遷移為初始態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交互的狀態機,結合上面列出的RTSP交互過程,describe和setup兩個方法將在初始態處理,當setup成功后,狀態變為就緒態,這個狀態下會給一些必要的屬性重新賦值,這些屬性是控制底層數據讀取的標志,之后變成PLAY態,這個狀態下會不斷從底層取數據處理并封裝發送到客戶端,一旦連接中斷,狀態又會變回初始態。下面我講一下各個方法的處理代碼。
    首先是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];/*根據收到的請求請求消息,跳過方法名,分離出URL*/if (!sscanf(pRtsp->in_buffer, " %*s %254s ", s8Url))//%*s表示第一個匹配到的%s被過濾掉{fprintf(stderr, "Error %s,%i\n", __FILE__, __LINE__);send_reply(400, 0, pRtsp); /* bad request */printf("get URL error");return ERR_NOERROR;}/*驗證URL */switch (ParseUrl(s8Url, server, &port, object, sizeof(object))){case 1: /*請求錯誤*/fprintf(stderr, "Error %s,%i\n", __FILE__, __LINE__);send_reply(400, 0, pRtsp);printf("url request error");return ERR_NOERROR;break;case -1: /*內部錯誤*/fprintf(stderr,"url error while parsing !\n");send_reply(500, 0, pRtsp);printf("inner error");return ERR_NOERROR;break;default:break;}/*取得序列號,并且必須有這個選項*/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); /*請求錯誤*/printf("get serial num 2 error");return ERR_NOERROR;}}//獲取SDP內容GetSdpDescr(pRtsp, s8Descr, s8Str);//發送Describe響應//printf("----------------1\r\n");SendDescribeReply(pRtsp, object, s8Descr, s8Str);//printf("2\r\n");return ERR_NOERROR;

    這里說一下一些常用的字段處理函數:
    ①strstr

    strstr(str1,str2)

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

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

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

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

    查找字符串s中首次出現字符c的位置。
    ④strncpy

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

    把src所指向的字符串中以src地址開始的前n個字節復制到dest所指的數組中,并返回被復制后的dest
    ⑤strcpy

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

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

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

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

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

    struct ifreq stIfr;//linux 網絡接口用來配置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");//創建任務描述的工具的名稱及版本號 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協議我這里不寫太多

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

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

    char *pMsgBuf; /* 用于獲取響應緩沖指針*/int s32MbLen;/* 分配空間,處理內部錯誤*/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); /*實體頭,表示實體類型*//*用于解析實體內相對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消息*//*向緩沖區中填充數據*/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;}//起始狀態為暫停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{//產生一個非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()函數

    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;//獲取本機網絡設備名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);}

    這個函數主要是創建了一個用于RTP傳輸的Socket,用來傳輸封裝好的RTP數據包,這一部分我會在第三部分講。另一個要注意的地方就是在

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

    schedule_add(rtp_s)這個函數里面給每一個連接的關鍵參數置位,這些參數是用來控制取底層數據并保存在緩沖區的判斷依據,還有就是設置了RtpSend()這個回調函數,這個回調函數會在Play階段被調用,用來封裝底層上來的碼流數據并發送。

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

    總結一下吧,關于RTSP交互這一塊,首先就是建立一個Socket用以接受發送RTSP的數據報文的,通過這個Socket進行我上面介紹過的服務器和客戶端的交互,在Setup階段創建新的Socket連接用來做具體的碼流數據傳輸,Play階段就是不斷的取底層數據進行封裝發送,TearDown階段斷開連接并釋放相關的指針或鏈表。

    關于具體的碼流是怎么封裝的我會在第三部分講。

    總結

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

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。