以下内容源于朱有鹏嵌入式课程的学习与整理,如有侵权请告知删除。
前言
博文第一季2:HI3518EV200的初体验中,所提供的测试文件sample_venc实现了基于RTSP协议的实时视频流传输功能。当时直接提供了二进制文件,现在我们来分析其对应的源码文件。
“基于RTSP协议的实时视频流传输”,以下统一简称“RTSP视频传输”。
一、RTSP视频传输实验
(1)将上面提到的源码文件解压到虚拟机的mpp/目录,并修改名字为sample_rtsp_ar0130(只是为了辨认方便)。
root@ubuntu:/home/xjh/iot/hisi_development/Hi3518E_SDK/Hi3518E_SDK_V1.0.3.0/mpp/sample_rtsp_ar0130# ls
audio common hifb ive Makefile Makefile.param readme region scene_auto tde venc vio
root@ubuntu:/home/xjh/iot/hisi_development/Hi3518E_SDK/Hi3518E_SDK_V1.0.3.0/mpp/sample_rtsp_ar0130#
(2)在mpp/sample_rtsp_ar0130/venc目录下执行make。
(3)接下来与第一季2:HI3518EV200的初体验中第一节第7点的(2)(3)一致,不再赘述。
二、源码分析
RTSP视频传输源码,是基于海思SDK的sample_venc.c来编写的,主要修改sample_venc.c文件、sample_comm_venc.c文件。后续可以对比两个sample的内容,深化对流程的理解。
1、函数调用关系
利用SI软件建立上述源码的工程,然后在sample_venc.c文件中找到main函数并分析调用情况:
mainRtspServer_init //VLC是客服端,HI3518E当做RTSP服务器,后者肯定要先运行起来RtspServerListen //线程RtspClientMsg//线程,针对每个客户端的线程ParseRequestString//解析消息头vdRTPSendThread //线程,有赖于RtspServer_init的内容,这里是传输数据VENC_Sent//真正发送的函数SAMPLE_VENC_720P_CLASSICSAMPLE_COMM_VENC_StartGetStream//和之前sample不同的地方SAMPLE_COMM_VENC_Sentjin//rtsp的发送VENC_Sent//真正发送的函数
2、分析RtspServer_init函数
(1)RtspServer_init函数内容如下。
void RtspServer_init(void)
{int i;pthread_t threadId = 0;memset(&g_rtp_playload,0,sizeof(g_rtp_playload));strcpy(&g_rtp_playload,"G726-32");g_audio_rate = 8000;pthread_mutex_init(&g_sendmutex,NULL);pthread_mutex_init(&g_mutex,NULL);pthread_cond_init(&g_cond,NULL);//g_rtspClients用来存储一些客户端的信息memset(&g_rtspClients,0,sizeof(RTSP_CLIENT)*MAX_RTSP_CLIENT);//pthread_create(&g_SendDataThreadId, NULL, SendDataThread, NULL);struct sched_param thdsched;thdsched.sched_priority = 2;//定义线程优先级//to listen visitingpthread_create(&threadId, NULL, RtspServerListen, NULL);//pthread_setschedparam(threadId,SCHED_RR,&thdsched);printf("RTSP:-----Init Rtsp server\n");pthread_create(&gs_RtpPid, 0, vdRTPSendThread, NULL);//exitok++;}
(2)RtspServerListen函数的内容与分析
void * RtspServerListen(void*pParam)
{int s32Socket;struct sockaddr_in servaddr;int s32CSocket;int s32Rtn;int s32Socket_opt_value = 1;int nAddrLen;struct sockaddr_in addrAccept;int bResult;memset(&servaddr, 0, sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl(INADDR_ANY);servaddr.sin_port = htons(RTSP_SERVER_PORT); //554,所以VLC设置那里端口也是554s32Socket = socket(AF_INET, SOCK_STREAM, 0);//这里使用TCPif (setsockopt(s32Socket ,SOL_SOCKET,SO_REUSEADDR,&s32Socket_opt_value,sizeof(int)) == -1) //设置一些属性 {return (void *)(-1);}s32Rtn = bind(s32Socket, (struct sockaddr *)&servaddr, sizeof(struct sockaddr_in));if(s32Rtn < 0){return (void *)(-2);}s32Rtn = listen(s32Socket, 50); if(s32Rtn < 0){return (void *)(-2);}nAddrLen = sizeof(struct sockaddr_in);int nSessionId = 1000;while ((s32CSocket = accept(s32Socket, (struct sockaddr*)&addrAccept, &nAddrLen)) >= 0)//accept是阻塞的,然后等待客服端的连接,连接上了就执行下面内容{printf("<<<<RTSP Client %s Connected...\n", inet_ntoa(addrAccept.sin_addr));int nMaxBuf = 10 * 1024; // 脧碌脥鲁艙芦禄谩路脰脜盲 2 x nMaxBuf 碌脛禄潞鲁氓沤贸脨隆if(setsockopt(s32CSocket, SOL_SOCKET, SO_SNDBUF, (char*)&nMaxBuf, sizeof(nMaxBuf)) == -1)printf("RTSP:!!!!!! Enalarge socket sending buffer error !!!!!!\n");int i;int bAdd=FALSE;for(i=0;i<MAX_RTSP_CLIENT;i++){if(g_rtspClients[i].status == RTSP_IDLE){memset(&g_rtspClients[i],0,sizeof(RTSP_CLIENT));g_rtspClients[i].index = i;g_rtspClients[i].socket = s32CSocket;g_rtspClients[i].status = RTSP_CONNECTED ;//RTSP_SENDING;g_rtspClients[i].sessionid = nSessionId++;strcpy(g_rtspClients[i].IP,inet_ntoa(addrAccept.sin_addr));pthread_t threadIdlsn = 0;struct sched_param sched;sched.sched_priority = 1;//to return ACKechopthread_create(&threadIdlsn, NULL, RtspClientMsg, &g_rtspClients[i]);//pthread_setschedparam(threadIdlsn,SCHED_RR,&sched);bAdd = TRUE;break;}}if(bAdd==FALSE){memset(&g_rtspClients[0],0,sizeof(RTSP_CLIENT));g_rtspClients[0].index = 0;g_rtspClients[0].socket = s32CSocket;g_rtspClients[0].status = RTSP_CONNECTED ;//RTSP_SENDING;g_rtspClients[0].sessionid = nSessionId++;strcpy(g_rtspClients[0].IP,inet_ntoa(addrAccept.sin_addr));pthread_t threadIdlsn = 0;struct sched_param sched;sched.sched_priority = 1;//to return ACKechopthread_create(&threadIdlsn, NULL, RtspClientMsg, &g_rtspClients[0]);//pthread_setschedparam(threadIdlsn,SCHED_RR,&sched);bAdd = TRUE;}//if(exitok){ exitok++;return NULL; } }if(s32CSocket < 0){// HI_OUT_Printf(0, "RTSP listening on port %d,accept err, %d\n", RTSP_SERVER_PORT, s32CSocket);}printf("----- INIT_RTSP_Listen() Exit !! \n");return NULL;
}
(3)RtspClientMsg函数内容与分析
void * RtspClientMsg(void*pParam)
{pthread_detach(pthread_self());//让线程自己回收自己int nRes;char pRecvBuf[RTSP_RECV_SIZE];RTSP_CLIENT * pClient = (RTSP_CLIENT*)pParam;//由传参而来memset(pRecvBuf,0,sizeof(pRecvBuf));printf("RTSP:-----Create Client %s\n",pClient->IP);while(pClient->status != RTSP_IDLE)//肯定不等于idle{nRes = recv(pClient->socket, pRecvBuf, RTSP_RECV_SIZE,0);//printf("-------------------%d\n",nRes);if(nRes < 1)//没有读到数据的异常情况{//usleep(1000);printf("RTSP:Recv Error--- %d\n",nRes);g_rtspClients[pClient->index].status = RTSP_IDLE;g_rtspClients[pClient->index].seqnum = 0;g_rtspClients[pClient->index].tsvid = 0;g_rtspClients[pClient->index].tsaud = 0;close(pClient->socket);break;}char cmdName[PARAM_STRING_MAX];char urlPreSuffix[PARAM_STRING_MAX];char urlSuffix[PARAM_STRING_MAX];char cseq[PARAM_STRING_MAX];
ParseRequestString(pRecvBuf,nRes,cmdName,sizeof(cmdName),urlPreSuffix,sizeof(urlPreSuffix),urlSuffix,sizeof(urlSuffix),cseq,sizeof(cseq));char *p = pRecvBuf;printf("<<<<<%s\n",p);//printf("\--------------------------\n");//printf("%s %s\n",urlPreSuffix,urlSuffix);if(strstr(cmdName, "OPTIONS")){OptionAnswer(cseq,pClient->socket);}else if(strstr(cmdName, "DESCRIBE")){DescribeAnswer(cseq,pClient->socket,urlSuffix,p);//printf("-----------------------------DescribeAnswer %s %s\n",// urlPreSuffix,urlSuffix);}else if(strstr(cmdName, "SETUP")){int rtpport,rtcpport;int trackID=0;SetupAnswer(cseq,pClient->socket,pClient->sessionid,urlPreSuffix,p,&rtpport,&rtcpport);sscanf(urlSuffix, "trackID=%u", &trackID);//printf("----------------------------------------------TrackId %d\n",trackID);if(trackID<0 || trackID>=2)trackID=0;g_rtspClients[pClient->index].rtpport[trackID] = rtpport;g_rtspClients[pClient->index].rtcpport= rtcpport;g_rtspClients[pClient->index].reqchn = atoi(urlPreSuffix);if(strlen(urlPreSuffix)<100)strcpy(g_rtspClients[pClient->index].urlPre,urlPreSuffix);//printf("-----------------------------SetupAnswer %s-%d-%d\n",// urlPreSuffix,g_rtspClients[pClient->index].reqchn,rtpport);}else if(strstr(cmdName, "PLAY")){PlayAnswer(cseq,pClient->socket,pClient->sessionid,g_rtspClients[pClient->index].urlPre,p);g_rtspClients[pClient->index].status = RTSP_SENDING;printf("Start Play\n",pClient->index);//printf("-----------------------------PlayAnswer %d %d\n",pClient->index);//usleep(100);}else if(strstr(cmdName, "PAUSE")){PauseAnswer(cseq,pClient->socket,p);}else if(strstr(cmdName, "TEARDOWN")){TeardownAnswer(cseq,pClient->socket,pClient->sessionid,p);g_rtspClients[pClient->index].status = RTSP_IDLE;g_rtspClients[pClient->index].seqnum = 0;g_rtspClients[pClient->index].tsvid = 0;g_rtspClients[pClient->index].tsaud = 0;close(pClient->socket);}//if(exitok){ exitok++;return NULL; } }printf("RTSP:-----Exit Client %s\n",pClient->IP);return NULL;
}
(4)ParseRequestString函数的内容与分析
int ParseRequestString(char const* reqStr,unsigned reqStrSize,char* resultCmdName,unsigned resultCmdNameMaxSize,char* resultURLPreSuffix,unsigned resultURLPreSuffixMaxSize,char* resultURLSuffix,unsigned resultURLSuffixMaxSize,char* resultCSeq,unsigned resultCSeqMaxSize)
{// This parser is currently rather dumb; it should be made smarter #####// Read everything up to the first space as the command name:int parseSucceeded = FALSE;unsigned i;for (i = 0; i < resultCmdNameMaxSize-1 && i < reqStrSize; ++i) {char c = reqStr[i];if (c == ' ' || c == '\t') {parseSucceeded = TRUE;break;}resultCmdName[i] = c;}resultCmdName[i] = '\0';if (!parseSucceeded) return FALSE;// Skip over the prefix of any "rtsp://" or "rtsp:/" URL that follows:unsigned j = i+1;while (j < reqStrSize && (reqStr[j] == ' ' || reqStr[j] == '\t')) ++j; // skip over any additional white spacefor (j = i+1; j < reqStrSize-8; ++j) {if ((reqStr[j] == 'r' || reqStr[j] == 'R')&& (reqStr[j+1] == 't' || reqStr[j+1] == 'T')&& (reqStr[j+2] == 's' || reqStr[j+2] == 'S')&& (reqStr[j+3] == 'p' || reqStr[j+3] == 'P')&& reqStr[j+4] == ':' && reqStr[j+5] == '/') {j += 6;if (reqStr[j] == '/') {// This is a "rtsp://" URL; skip over the host:port part that follows:++j;while (j < reqStrSize && reqStr[j] != '/' && reqStr[j] != ' ') ++j;} else {// This is a "rtsp:/" URL; back up to the "/":--j;}i = j;break;}}// Look for the URL suffix (before the following "RTSP/"):parseSucceeded = FALSE;unsigned k;for (k = i+1; k < reqStrSize-5; ++k) {if (reqStr[k] == 'R' && reqStr[k+1] == 'T' &&reqStr[k+2] == 'S' && reqStr[k+3] == 'P' && reqStr[k+4] == '/') {while (--k >= i && reqStr[k] == ' ') {} // go back over all spaces before "RTSP/"unsigned k1 = k;while (k1 > i && reqStr[k1] != '/' && reqStr[k1] != ' ') --k1;// the URL suffix comes from [k1+1,k]// Copy "resultURLSuffix":if (k - k1 + 1 > resultURLSuffixMaxSize) return FALSE; // there's no roomunsigned n = 0, k2 = k1+1;while (k2 <= k) resultURLSuffix[n++] = reqStr[k2++];resultURLSuffix[n] = '\0';// Also look for the URL 'pre-suffix' before this:unsigned k3 = --k1;while (k3 > i && reqStr[k3] != '/' && reqStr[k3] != ' ') --k3;// the URL pre-suffix comes from [k3+1,k1]// Copy "resultURLPreSuffix":if (k1 - k3 + 1 > resultURLPreSuffixMaxSize) return FALSE; // there's no roomn = 0; k2 = k3+1;while (k2 <= k1) resultURLPreSuffix[n++] = reqStr[k2++];resultURLPreSuffix[n] = '\0';i = k + 7; // to go past " RTSP/"parseSucceeded = TRUE;break;}}if (!parseSucceeded) return FALSE;// Look for "CSeq:", skip whitespace,// then read everything up to the next \r or \n as 'CSeq':parseSucceeded = FALSE;for (j = i; j < reqStrSize-5; ++j) {if (reqStr[j] == 'C' && reqStr[j+1] == 'S' && reqStr[j+2] == 'e' &&reqStr[j+3] == 'q' && reqStr[j+4] == ':') {j += 5;unsigned n;while (j < reqStrSize && (reqStr[j] == ' ' || reqStr[j] == '\t')) ++j;for (n = 0; n < resultCSeqMaxSize-1 && j < reqStrSize; ++n,++j) {char c = reqStr[j];if (c == '\r' || c == '\n') {parseSucceeded = TRUE;break;}resultCSeq[n] = c;}resultCSeq[n] = '\0';break;}}if (!parseSucceeded) return FALSE;return TRUE;
}
3、分析vdRTPSendThread函数
(1)vdRTPSendThread函数的内容如下:
HI_VOID* vdRTPSendThread(HI_VOID *p)
{while(1){if(!list_empty(&RTPbuf_head)){RTPbuf_s *p = get_first_item(&RTPbuf_head,RTPbuf_s,list);VENC_Sent(p->buf,p->len);list_del(&(p->list));free(p->buf);free(p);p = NULL;count--;//printf("count = %d\n",count);}usleep(5000);}
}