网络摄像头RTSP直播方案(三)

前面的部分讲了关于RTSP连接的交互过程,在RTSP推流的过程中,RTSP协议只是做一个控制作用,底层真正进行传输的流媒体协议还是RTP协议。做这一部分主要是要先了解RTP协议的封装格式,这里我不详细讲了,网上有很多博客都有讲,我这里主要是讲一下实现方式。
在建立了RTSP连接之后,就是在客户端发回PLAY指令之后,在setup阶段被设置的回调函数会被调用,下面是回调函数的代码

unsigned int RtpSend(unsigned int u32Rtp, unsigned char *pData, int s32DataSize, unsigned int u32TimeStamp)
{
    int s32NalSize = 0;
    int nalhead_pos = 0,naltail_pos = 0;
    HndRtp hRtp = (HndRtp)u32Rtp;
	unsigned char * nalubuffer = NULL;
    

    hRtp->u32TimeStampCurr = u32TimeStamp;

    while(nalhead_pos<s32DataSize)
    {
        if(pData[nalhead_pos++] == 0x00 && 
			pData[nalhead_pos++] == 0x00) 
		{ 	
				if(pData[nalhead_pos++] == 0x00 && 
					pData[nalhead_pos++] == 0x01 ){
				   goto gotnal_head;
				}
				
				else
					continue;
		 }
		else 
			continue;

gotnal_head:
		naltail_pos = nalhead_pos;  
		
		while (naltail_pos<s32DataSize)  
		{  
			if(pData[naltail_pos++] == 0x00 && 
				pData[naltail_pos++] == 0x00 )
			{  
					if(pData[naltail_pos++] == 0x00 &&
						pData[naltail_pos++] == 0x01)
					{	
						s32NalSize = (naltail_pos-4)-nalhead_pos;
						break;
					}
			}  
		}
		
      if(nalhead_pos<s32DataSize && naltail_pos<s32DataSize){
        nalubuffer=(unsigned char*)malloc(s32NalSize);
	    memset(nalubuffer,0,s32NalSize);
		memcpy(nalubuffer,pData+nalhead_pos,s32NalSize);
		//SendNalu264(hRtp, nalubuffer,s32NalSize);
		test_SendNalu264(hRtp, nalubuffer,s32NalSize);
		free(nalubuffer);
		nalhead_pos = naltail_pos-4;
      }

	  if(nalhead_pos<s32DataSize && naltail_pos>=s32DataSize){
	  	s32NalSize = s32DataSize -nalhead_pos;
        nalubuffer=(unsigned char*)malloc(s32NalSize);
	    memset(nalubuffer,0,s32NalSize);
		memcpy(nalubuffer,pData+nalhead_pos,s32NalSize);
		//SendNalu264(hRtp, nalubuffer,s32NalSize);
		test_SendNalu264(hRtp, nalubuffer,s32NalSize);
		free(nalubuffer);
		nalhead_pos = naltail_pos;
      }
		
		
    }//while
                    
    return 0;
}

这个函数主要是把每帧里面的00 00 00 01开始标志去掉,然后把数据放到test_SendNalu264()这个函数进行封装。码流数据是从底层取出来放在一个环形缓冲区里面的,这个我单独写了一个线程,实现方式比较简单,这边不单独介绍了,主要说一下test_SendNalu264()这个封包函数吧

static int test_SendNalu264(HndRtp hRtp, unsigned char *pNalBuf, int s32NalBufSize)
{
    unsigned char *nalu_buf;
    nalu_buf = pNalBuf;
	unsigned char *pSendBuf;
	int ret = 0;
    int len_sendbuf;
    unsigned char FirstNaluBytes;
	int fu_pack_num;        /* nalu 需要分片发送时分割的个数 */
    int last_fu_pack_size;  /* 最后一个分片的大小 */
    int fu_seq;             /* fu-A 序号 */
	struct timeval now;
    unsigned long long ts;
    pSendBuf = (unsigned char *)calloc(MAX_RTP_PKT_LENGTH + 100, sizeof(unsigned char));
    if(NULL == pSendBuf)
    {
        ret = -1;
        goto cleanup;
    }
	  gettimeofday(&now,NULL);
      ts = (now.tv_sec*1000 + now.tv_usec/1000);
	  if(firstflag){
        F_ts = ts;
	    firstflag = 0;
	    ts = ts *90;
	    printf("Run in set firstflag !!!!!\n");
      }//毫秒
      else
      ts = (ts - F_ts)*90;
     
	
     //ts_current += (90000 / 25);  /* 90000 / 25 = 3600 */
	 hRtp->pRtpFixedHdr = (StRtpFixedHdr *)pSendBuf;
	 hRtp->pRtpFixedHdr->u4CSrcLen   = 0;
	 hRtp->pRtpFixedHdr->u1Externsion  = 0;
	 hRtp->pRtpFixedHdr->u1Padding   =0;
     hRtp->pRtpFixedHdr->u7Payload   = H264;
     hRtp->pRtpFixedHdr->u2Version   = 2;
	 hRtp->pRtpFixedHdr->u32TimeStamp  = BigLittleSwap32(ts);
	 hRtp->pRtpFixedHdr->u32SSrc = hRtp->u32SSrc;

	
    FirstNaluBytes = *pNalBuf;
	
	if(s32NalBufSize < 1 ||(FirstNaluBytes&0x1f) == 0x08 ||(FirstNaluBytes&0x1f) == 0x07){
	     //ts = ts -2*(90000 / 25);	
       goto cleanup;
	}
	 /* if(S_ts){
          ts = S_ts;
	      S_ts = 0;
	  }*/
    
  /* if((FirstNaluBytes&0x1f) == 0x07){
        hRtp->pRtpFixedHdr->u1Marker    = 0;
		//hRtp->pRtpFixedHdr->u16SeqNum   = BigLittleSwap16((hRtp->u16SeqNum++));
	        S_ts = ts;
		if(sps_len>0)
		{	
		    hRtp->pRtpFixedHdr->u16SeqNum   = BigLittleSwap16(hRtp->u16SeqNum);
			hRtp->u16SeqNum++;
	        memcpy(pSendBuf+12, sps_tmp, sps_len);
	        if(sendto(hRtp->s32Sock, pSendBuf, sps_len+12, 0, (struct sockaddr *)&hRtp->stServAddr, sizeof(hRtp->stServAddr)) < 0)
	        {
	            ret = -1;
	            goto cleanup;
	        }
			//printf("Send sps success!!\n");
			//printf("send sps u16SeqNum = %u\n",hRtp->u16SeqNum);
		}
		if(pps_len>0)
		{	    
		         
		    hRtp->pRtpFixedHdr->u16SeqNum   = BigLittleSwap16(hRtp->u16SeqNum);
			hRtp->u16SeqNum++;
	        memcpy(pSendBuf+12, pps_tmp, pps_len);
	        if(sendto(hRtp->s32Sock, pSendBuf, pps_len+12, 0, (struct sockaddr *)&hRtp->stServAddr, sizeof(hRtp->stServAddr)) < 0)
	        {
	            ret = -1;
	            goto cleanup;
	        }
			//printf("Send pps success !!\n");
			//printf("send pps u16SeqNum = %u\n",hRtp->u16SeqNum);
		}
		goto cleanup;
    }*/
	  

	if(s32NalBufSize <= MAX_RTP_PKT_LENGTH){
      hRtp->pRtpFixedHdr->u1Marker    = 1;
	  hRtp->pRtpFixedHdr->u16SeqNum   = BigLittleSwap16(hRtp->u16SeqNum);
	  hRtp->u16SeqNum++;
	  hRtp->pNaluHdr                  = (StNaluHdr *)(pSendBuf + 12);
      hRtp->pNaluHdr->u1F             = (FirstNaluBytes & 0x80) >> 7;
      hRtp->pNaluHdr->u2Nri           = (FirstNaluBytes & 0x60) >> 5;
      hRtp->pNaluHdr->u5Type          = FirstNaluBytes & 0x1f;
	  memcpy(pSendBuf + 13, nalu_buf + 1, s32NalBufSize - 1);
      len_sendbuf = 12 + s32NalBufSize;
      if(sendto(hRtp->s32Sock, pSendBuf, len_sendbuf, 0, (struct sockaddr *)&hRtp->stServAddr, sizeof(hRtp->stServAddr)) < 0)
      {
            ret = -1;
            goto cleanup;
      }
	   //printf("send one packet u16SeqNum = %u\n",hRtp->u16SeqNum);
	   goto cleanup;
	}else{
            fu_pack_num = s32NalBufSize % MAX_RTP_PKT_LENGTH ? (s32NalBufSize / MAX_RTP_PKT_LENGTH + 1) : s32NalBufSize / MAX_RTP_PKT_LENGTH;
            last_fu_pack_size = s32NalBufSize % MAX_RTP_PKT_LENGTH ? s32NalBufSize % MAX_RTP_PKT_LENGTH : MAX_RTP_PKT_LENGTH;
            fu_seq = 0;
			//printf("fu_pack_num = %d \n",fu_pack_num);
			//printf("Send for FU-A !!!\n");
			for (fu_seq = 0; fu_seq < fu_pack_num; fu_seq++) {
                 if (fu_seq == 0) {
	               hRtp->pRtpFixedHdr->u16SeqNum   = BigLittleSwap16(hRtp->u16SeqNum);
			       hRtp->u16SeqNum++;
	               hRtp->pRtpFixedHdr->u1Marker    = 0;
				  //指定fu indicator位置
                   hRtp->pFuInd            = (StFuIndicator *)(pSendBuf + 12);
                   hRtp->pFuInd->u1F       = (FirstNaluBytes & 0x80) >> 7;
                   hRtp->pFuInd->u2Nri     = (FirstNaluBytes & 0x60) >> 5;
                   hRtp->pFuInd->u5Type    = 28;

                   //指定fu header位置
                   hRtp->pFuHdr            = (StFuHdr *)(pSendBuf + 13);
                   hRtp->pFuHdr->u1S       = 1;
				   hRtp->pFuHdr->u1E       = 0;
				   hRtp->pFuHdr->u1R       = 0;
                   hRtp->pFuHdr->u5Type    = FirstNaluBytes & 0x1f;

				   memcpy(pSendBuf + 14, nalu_buf + 1, MAX_RTP_PKT_LENGTH - 1);
				   len_sendbuf = 12 + 2 + (MAX_RTP_PKT_LENGTH - 1);
				   if(sendto(hRtp->s32Sock, pSendBuf, len_sendbuf, 0, (struct sockaddr *)&hRtp->stServAddr, sizeof(hRtp->stServAddr)) < 0)
                   {
                       ret = -1;
                       goto cleanup;
                   }
				      //printf("send FU-A First packet u16SeqNum = %u\n",hRtp->u16SeqNum);
				      //printf("Send FU-A First packet!! TimeStamp = %ld fu_seq = %d ts_current = %d\n",hRtp->pRtpFixedHdr->u32TimeStamp,fu_seq,ts_current);
                 }else if (fu_seq < fu_pack_num - 1) { 
	               hRtp->pRtpFixedHdr->u16SeqNum   = BigLittleSwap16(hRtp->u16SeqNum);
			       hRtp->u16SeqNum++;
	               hRtp->pRtpFixedHdr->u1Marker    = 0;
				  //指定fu indicator位置
                   hRtp->pFuInd            = (StFuIndicator *)(pSendBuf + 12);
                   hRtp->pFuInd->u1F       = (FirstNaluBytes & 0x80) >> 7;
                   hRtp->pFuInd->u2Nri     = (FirstNaluBytes & 0x60) >> 5;
                   hRtp->pFuInd->u5Type    = 28;

                   //指定fu header位置
                   hRtp->pFuHdr            = (StFuHdr *)(pSendBuf + 13);
                   hRtp->pFuHdr->u1S       = 0;
				   hRtp->pFuHdr->u1E       = 0;
				   hRtp->pFuHdr->u1R       = 0;
                   hRtp->pFuHdr->u5Type    = FirstNaluBytes & 0x1f;

				   memcpy(pSendBuf + 14, nalu_buf + MAX_RTP_PKT_LENGTH * fu_seq, MAX_RTP_PKT_LENGTH); 
                   len_sendbuf = 12 + 2 + MAX_RTP_PKT_LENGTH;
                   if(sendto(hRtp->s32Sock, pSendBuf, len_sendbuf, 0, (struct sockaddr *)&hRtp->stServAddr, sizeof(hRtp->stServAddr)) < 0)
                   {
                    ret = -1;
                    goto cleanup;
                   }
				   //printf("send FU-A mid packet u16SeqNum = %u\n",hRtp->u16SeqNum);
                    //printf("Send FU-A mid Packet !!! TimeStamp = %ld fu_seq = %d ts_current = %d\n",hRtp->pRtpFixedHdr->u32TimeStamp,fu_seq,ts_current);
				 }else{
				     
	               hRtp->pRtpFixedHdr->u16SeqNum   = BigLittleSwap16(hRtp->u16SeqNum);
			       hRtp->u16SeqNum++;
	               hRtp->pRtpFixedHdr->u1Marker    = 1;
				  //指定fu indicator位置
                   hRtp->pFuInd            = (StFuIndicator *)(pSendBuf + 12);
                   hRtp->pFuInd->u1F       = (FirstNaluBytes & 0x80) >> 7;
                   hRtp->pFuInd->u2Nri     = (FirstNaluBytes & 0x60) >> 5;
                   hRtp->pFuInd->u5Type    = 28;

                   //指定fu header位置
                   hRtp->pFuHdr            = (StFuHdr *)(pSendBuf + 13);
                   hRtp->pFuHdr->u1S       = 0;
				   hRtp->pFuHdr->u1E       = 1;
				   hRtp->pFuHdr->u1R       = 0;
                   hRtp->pFuHdr->u5Type    = FirstNaluBytes & 0x1f;
				   memcpy(pSendBuf + 14, nalu_buf + MAX_RTP_PKT_LENGTH * fu_seq, last_fu_pack_size);
				   len_sendbuf = 12 + 2 + last_fu_pack_size;
				   if(sendto(hRtp->s32Sock, pSendBuf, len_sendbuf, 0, (struct sockaddr *)&hRtp->stServAddr, sizeof(hRtp->stServAddr)) < 0)
                   {
                    ret = -1;
                    goto cleanup;
                   }
				   //printf("Send FU-A last packet u16SeqNum = %u\n",hRtp->u16SeqNum);
				   //printf("Send FU-A last Packet !!! TimeStamp = %ld fu_seq = %d ts_current = %d\n",hRtp->pRtpFixedHdr->u32TimeStamp,fu_seq,ts_current);
				 }

			}
              goto cleanup;
	}


	

cleanup:
    if(pSendBuf)
    {
        free((void *)pSendBuf);
		pSendBuf = NULL;
    }
	//printf("\n");
	//fflush(stdout);

    return ret;	
}

因为一次性发送的数据长度有限制,所以长度大于1400的帧采用分片FU-A的方式来封装,小于1400的以单包来封,上面的代码是根据官方的封包要求来写的,单包封装只需要给原始码流打上NALU头之后就可以发送了,分片比较复杂,在打了NALU头之后还要打上FU indicator和FU header,要注意的主要是FU header头的S E 标志位,S代表这一片是这一帧的第一片,E代表这一片是这一帧的最后一片,我代码里面是把第一片,中间片,和最后一片分来来写的,这样清楚一点。
有两点要注意一下

  1. 时间戳
    时间戳一定要打对,不然很有可能播不出来,两个时间戳之间应该差采样率/帧率,最好获取系统时间来打,我试过固定的递增时间戳也是不行的
  2. 关于 SPS PPS的发送
    上面函数有一部分注释掉了,是因为我在RTSP连接的时候,已经将BASE64编码后的SPS PPS发送到客户端的,所以后面不需要再发,SPS PPS只需要发一次,有一点要注意,如果你是在这个函数发SPS PPS,这两包的序列号要一致。

这一套RTSP直播的DEMO我已经放在我的下载资源里面了,有兴趣可以看一下。在Realtek底层下运行的,要换底层的话只需要将初始化及获取底层数据那一块的接口进行更换就可以了,注意不同的底层编码芯片出来的数据结构有可能不一样,可能需要调整封包逻辑。

你可能感兴趣的:(RTSP)