欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

ONVIF网络摄像头(IPC)客户端开发—RTSP RTCP RTP加载H264数据

程序员文章站 2022-07-14 18:47:29
...

前言:

    RTSP,RTCP,RTP一般是一起使用,在FFmpeg和live555这些库中,它们为了更好的适用性,所以实现起来非常复杂,直接查看FFmpeg和Live555源代码来熟悉这些协议非常吃力,这里将它们独立出来实现,以便更好的理解协议。本文主要介绍RTSP,RTCP,RTP加载H264数据流。

说明:

    (1)大华IPC摄像头作为服务端
    (2)在ubuntu16.04中编译实现测试程序
    (3)服务端IP: 192.168.0.120
    (4)客户端IP: 192.168.0.128

协议介绍:

    用一句简单的话总结:RTSP发起/终结流媒体、RTP传输流媒体数据 、RTCP对RTP进行控制,同步。RTSP属于四层网络当中的应用层,RTP,RTCP属于传输层,如下图所示意。

ONVIF网络摄像头(IPC)客户端开发—RTSP RTCP RTP加载H264数据

   RTSP在博客《ONVIF网络摄像头(IPC)客户端开发—最简RTSP客户端实现》中已经介绍,这里不再重复,这里主要介绍RTCP和RTP。

RTCP协议:

    RTCP控制协议需要与RTP数据协议一起配合使用,当应用程序启动一个RTP会话时将同时占用两个端口,分别供RTP和RTCP使用,其中RTCP端口一定要是基数,RTP端口一定要是偶数,且是两个相邻的端口。RTP本身并不能为按序传输数据包提供可靠的保证,也不提供流量控制和拥塞控制,这些都由RTCP来负责完成。通常RTCP会采用与RTP相同的分发机制,向会话中的所有成员周期性地发送控制信息,应用程序通过接收这些数据,从中获取会话参与者的相关资料,以及网络状况、分组丢失概率等反馈信息,从而能够对服务质量进行控制或者对网络状况进行诊断。
    RTCP协议的功能是通过不同的RTCP数据报来实现的,主要有如下几种类型:

  • SR:发送端报告,所谓发送端是指发出RTP数据报的应用程序或者终端,发送端同时也可以是接收端。
  • RR:接收端报告,所谓接收端是指仅接收但不发送RTP数据报的应用程序或者终端。
  • SDES:源描述,主要功能是作为会话成员有关标识信息的载体,如用户名、邮件地址、电话号码等,此外还具有向会话成员传达会话控制信息的功能。
  • BYE:通知离开,主要功能是指示某一个或者几个源不再有效,即通知会话中的其他成员自己将退出会话。
  • APP:由应用程序自己定义,解决了RTCP的扩展性问题,并且为协议的实现者提供了很大的灵活性。

    如果只是测试加载H264数据流,这里建立RTCP连接后不发送RTCP数据包也是可以的,那就是客户端和服务端都采用默认的发送和接收方式收发数据。下面的测试程序实现了RTCP协议,但是没有发送RTCP数据包。

RTP协议:

    在加载H264数据流的时候,比较麻烦的处理环节就是RTP网络数据包的解析,这个需要完全按照协议来解析,否则会出现花屏现象或是显示不出来的情况。RTP网络数据包结构图如下:

ONVIF网络摄像头(IPC)客户端开发—RTSP RTCP RTP加载H264数据

这里对上图中的名词做个解析:

H264图像结构:

  • NAL       Network Abstraction Layer 
  • NALU    Network Abstraction Layer Unit
  • VCL       Video Coding Layer
  • VCLU    Video Coding Layer Unit

    H264的图像数据是在NAL里面,并且上图中00 00 00 01 6X 这些数据是解包的时候再添加上去的,在RTP网络包中并不会传输这些信息,但是正常的h264图像数据中是需要这些标签来识别不同的图片帧的。

RTP分包模式:

  • SNP     Single NALU Packet
  • AP        Aggregation Packet
  • FU       Fragmentation Units

    上面说过RTP网络传输包一包大小为1500字节,那么对于大于1500字节的视频帧,比如I帧,那就需要分开很多包来传输,所采用的就是FU分片模式。如果数据包小于1500字节,比如SPS,PPS,SEI数据包,直接一个RTP包就可以发送完,那使用的就是SNP单包模式。另外组合包AP表示一个包里有多种视频帧类型。在这里我们用到的是单包SNP和分片包FU两种模式。

RTP Pack: 

    RTP协议从网络中接收或是发送的一个数据包,一般最大1500字节。实际解析的时候也是以这样的一个数据包为单位来解析。

RTP Header:

    RTP网络数据包的包头,长度为12字节具体格式如下:

/***************************************************************
0                   1                   2                   3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|V=2|P|X|  CC   |M|     PT      |       sequence number         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                           timestamp                           |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|           synchronization source (SSRC) identifier            |
+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
|            contributing source (CSRC) identifiers             |
|                             ....                              |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*****************************************************************/ 
/**RTP 头结构**/
typedef struct 
{
    /** byte 0 **/
    unsigned char bit4CsrcLen:4;        /** expect 0 **/
    unsigned char bit1Extension:1;      /** expect 1, see RTP_OP below **/
    unsigned char bit1Padding:1;        /** expect 0 **/
    unsigned char bit1Version:2;        /** expect 2 **/
    /** byte 1 **/
    unsigned char bit7PayLoadType:7;    /** RTP_PAYLOAD_RTSP **/
    unsigned char bit1Marker:1;         /** expect 1 **/
    /** bytes 2,3 **/
    unsigned int u32SeqNum;             /** RTP sequence number **/     
    /** bytes 4-7 **/
    unsigned int u32TimeStamp;          /** RTP sequence number **/
    /** bytes 8-11 **/
    unsigned int u32Ssrc;               /**stream number is used here **/
}RTP_HEADER_S;

RTP payload:

   RTP加载的实际数据,可以是视频,也可以是音频,也可以是其他类型,这里加载的是h264视频数据,对应下面的96。数据类型如下:

/***********************************************************
PT      encoding    media type  clock rate
		name                    (Hz)
_____________________________________________
24      unassigned  V
25      CelB        V           90,000
26      JPEG        V           90,000
27      unassigned  V
28      nv          V           90,000
29      unassigned  V
30      unassigned  V
31      H261        V           90,000
32      MPV         V           90,000
33      MP2T        AV          90,000
34      H263        V           90,000
35-71   unassigned  ?
72-76   reserved    N/A         N/A
77-95   unassigned  ?
96-127  dynamic     ?
dyn     H263-1998   V           90,000

Table 5: Payload types (PT) for video and combined
		encodings
***********************************************************/

NALU Header:

    网络抽象层单元头结构体

/***************** 
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|F|NRI|  Type   |
+---------------+
*****************/
typedef struct 
{
    unsigned char bit5TYPE:5;
    unsigned char bit2NRI:2;
    unsigned char bit1F:1;        
}RTP_NALU_HEADER_S;

FU Indicator:

    分片包指示符

/****************
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|F|NRI|  Type   |
+---------------+
*****************/
typedef struct 
{
	unsigned char Bit5TYPE:5;
	unsigned char BitNRI:2; 
	unsigned char BitF:1;              
}RTP_FU_INDICATOR_S; 

FU Header:

    分片包头结构

/******************
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|S|E|R|  Type   |
+---------------+
*******************/
typedef struct 
{
	unsigned char Bit5TYPE:5;
	unsigned char Bit1R:1;
	unsigned char Bit1E:1;
	unsigned char Bit1S:1;    
}RTP_FU_HEADER_S;

设计思路:

  1.     通过ONVIF协议获取IPC网络摄像头RTSP的URL地址,为了简化测试程序,不在这里介绍,本文中使用固定地址:rtsp://192.168.0.120:554/cam/realmonitor?channel=1&subtype=0&unicast=true&proto=Onvif
  2.     通过RTSP协议发起数据流,同时获取服务端的RTCP和RTP的网络端口号,数据类型等信息。
  3.     建立RTCP,RTP连接,RTCP是TCP连接,RTP在这里走的是UDP连接。
  4.     客户端接收RTP网络数据包
  5.     对RTP客户端接收到的网络数据包进行解包
  6.     将解包之后的数据写入文件,对于H264视频帧的第一包数据,需要添加上H264帧头标签00 00 00 01 6X信息。
  7.     为方便其他地方使用,可以将视频数据按帧添加进队列。

代码实现:

   完整代码结构如下:

ONVIF网络摄像头(IPC)客户端开发—RTSP RTCP RTP加载H264数据

 这里贴出rtp_client.c的代码,主要实现RTP客户端对RTP网络包进行解包,存储功能

rtp_client.c

/************************************************************
*Copyright (C),lcb0281at163.com lcb0281atgmail.com
*FileName: rtp_client.c
*BlogAddr: https://blog.csdn.net/li_wen01
*Description: RTP 协议 
*Date:	   2019-10-05
*Author:   Caibiao Lee
*Version:  V1.0
*Others:
*History:
***********************************************************/
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>

#include "rtp_client.h"


#define RTP_RECV_DATA_LEN       1500

/**for debug **/
FILE * g_WriteFd = NULL;


/******************************************************** 
Function:    IPC_RTP_GetCompleteFrame  
Description: 初始化RTP数据解包参数
Input:  *pstRTPInfo 
OutPut: *pstRTPInfo
Return: 0 成功,非0失败
Others: 
Author: Caibiao Lee
Date:   2019-10-05
*********************************************************/
int RTP_Client_Init(RTP_STATUS_S *pstRTPClient)
{
	if(NULL==pstRTPClient)
	{
		printf("%s %d input para error \n",__FUNCTION__,__LINE__);
		return -1;
	}

	pstRTPClient->bRTPState = false;
	memset(pstRTPClient->arrs8SessionId,0,sizeof(pstRTPClient->arrs8SessionId));
	pstRTPClient->s32RTPSockFd = -1;

	return 0;
}

/******************************************************** 
Function:    RTP_Client_Release  
Description: 释放RTP协议申请的资源
Input:  *pstRTPInfo 
OutPut: *pstRTPInfo
Return: 0 成功,非0失败
Others: 
Author: Caibiao Lee
Date:   2019-10-05
*********************************************************/
int RTP_Client_Release(RTP_STATUS_S *pstRTPClient)
{
	
	pstRTPClient->bRTPState = false;
    if(pstRTPClient->s32RTPSockFd>0)
    {
        NET_SocketClose(pstRTPClient->s32RTPSockFd);
        pstRTPClient->s32RTPSockFd = -1;
    }

    if(NULL!=g_WriteFd)
    {
        fclose(g_WriteFd);
    }

	return 0;
}

/******************************************************** 
Function:    IPC_RTP_Session  
Description: 建立RTP会话
Input:  *pstRTPClient
OutPut: *pstRTPClient
Return: 0 成功,非0失败
Others: 
    1.RTP 这里建立的是UDP连接
Author: Caibiao Lee
Date:   2019-10-05
*********************************************************/
int RTP_CLient_Session(RTP_STATUS_S *pstRTPClient)
{
    int l_s32Sockfd;
    int l_s32Ret;
    unsigned int l_u32ClientPort;
    unsigned int l_u328ServPort;
    bool l_bRTPState;

	if(NULL==pstRTPClient)
	{
		printf("%s %d input para error \n",__FUNCTION__,__LINE__);
		return -1;
	}

	l_s32Sockfd     = pstRTPClient->s32RTPSockFd;
	l_u32ClientPort = RTP_CLIENT_PORT;
	l_u328ServPort  = pstRTPClient->u32SerRTPPort;
	l_bRTPState     = pstRTPClient->bRTPState;
	
	if(true == l_bRTPState)
	{
		printf("%s %d RTP is already start \n",__FUNCTION__,__LINE__);
		return 0;
	}
	
	if(l_s32Sockfd > 0)
	{
		NET_SocketClose(l_s32Sockfd);
	}

	/**建立UDP连接**/
    l_s32Sockfd = NET_SocketCreate(SOCK_DGRAM);
    if(l_s32Sockfd < 0)
    {
        printf("%s %d Socket create error",__FUNCTION__,__LINE__);
        return -3;
    }
	
	printf("%s %d ID = %d,ClientPort = %d,ServPort = %d,Server IP = %s\n",
		__FUNCTION__,__LINE__,l_s32Sockfd,l_u32ClientPort,l_u328ServPort,
		pstRTPClient->arrs8ServerIP);
	
    l_s32Ret = NET_SocketBind(l_s32Sockfd,l_u32ClientPort);
    if(l_s32Ret < 0)
    {
        printf("%s %d Socket Bind error\n",__FUNCTION__,__LINE__);
        return -4;
    }
   
    l_s32Ret = NET_SocketConnect(l_s32Sockfd,pstRTPClient->arrs8ServerIP,l_u328ServPort);
    if(l_s32Ret < 0)
    {
        printf("%s %d Socket Connect Error \n",__FUNCTION__,__LINE__);
        return -5;
    }

	pstRTPClient->bRTPState = true;
	pstRTPClient->s32RTPSockFd = l_s32Sockfd;
	
    return 0;

}

/******************************************************** 
Function:    RTP_Client_H264StreamStore  
Description: 存储H264流数据
Input:  pstRTPInfo 
OutPut: pstRTPInfo
Return: 0 成功,非0失败
Others: 
    1.h264 RTP 流数据没有 00 00 00 01标签,存储的时候需要将
      该标签添加上。
    2.在这里添加SPS,PPS,SEI 帧类型标签,其他帧类型在解包时已添加
    3.为避免乱码,数据流要等到接收了SPS,PPS之后才接收其他视频帧
Author: Caibiao Lee
Date:   2019-10-05
*********************************************************/
int RTP_Client_H264StreamStore(RTP_UNPACK_S stRtpUnpack)
{
    static bool ls_bWriteFlag = false;
#if 0    
    printf("\n\n");
    printf("eFrameType    :%d \n",stRtpUnpack.eFrameType);
    printf("u8OutNaluType :%d \n",stRtpUnpack.u8OutNaluType);
    printf("u8Version     :%d \n",stRtpUnpack.stOutRTPPack.u8Version);
    printf("u8Padding     :%d \n",stRtpUnpack.stOutRTPPack.u8Padding);
    printf("u8Extension   :%d \n",stRtpUnpack.stOutRTPPack.u8Extension);
    printf("u8Cc          :%d \n",stRtpUnpack.stOutRTPPack.u8Cc);
    printf("u8Marker      :%d \n",stRtpUnpack.stOutRTPPack.u8Marker);
    printf("u8Pt          :%d \n",stRtpUnpack.stOutRTPPack.u8Pt);
    printf("u32SeqNum     :%d \n",stRtpUnpack.stOutRTPPack.u32SeqNum);
    printf("u32TimeStamp  :%d \n",stRtpUnpack.stOutRTPPack.u32TimeStamp);
    printf("u32Ssrc       :%u \n",stRtpUnpack.stOutRTPPack.u32Ssrc);
    printf("pu32Paylen    :%d \n",stRtpUnpack.stOutRTPPack.u32Paylen);
#endif

    unsigned char l_arrH264Flag[5] = {0};
    
    switch(stRtpUnpack.eFrameType)
    {
        case NAL_PACK: /**单包**/
        {
            break;
        }

        case AP_PACK:  /**组合包**/
        {
            break;
        }
        
        case FU_START_PACK:
        {
            l_arrH264Flag[0] = 0x00;
            l_arrH264Flag[1] = 0x00;    
            l_arrH264Flag[2] = 0x00;
            l_arrH264Flag[3] = 0x01;

            if((stRtpUnpack.stOutRTPPack.u32Paylen>0)&&(true==ls_bWriteFlag))
            {
                /**注意长度,ls_arrH264Flag[4] 的值在接收时已经添加上了**/
                fwrite(l_arrH264Flag,1,4,g_WriteFd);
            }

            break;
        }

        case FU_MIDllE_PACK:
        {
            break;
        }

        case FU_END_PACK:
        {
            
            break;
        }

        case H264_SEI:
        {
            l_arrH264Flag[0] = 0x00;
            l_arrH264Flag[1] = 0x00;    
            l_arrH264Flag[2] = 0x00;
            l_arrH264Flag[3] = 0x01;
            l_arrH264Flag[4] = 0x66;
            fwrite(l_arrH264Flag,1,5,g_WriteFd);
            ls_bWriteFlag = true;
            break;
        }

        case H264_PPS:
        {
            l_arrH264Flag[0] = 0x00;
            l_arrH264Flag[1] = 0x00;    
            l_arrH264Flag[2] = 0x00;
            l_arrH264Flag[3] = 0x01;
            l_arrH264Flag[4] = 0x68;
            fwrite(l_arrH264Flag,1,5,g_WriteFd);
            ls_bWriteFlag = true;
            break;

        }

        case H264_SPS:
        {
            l_arrH264Flag[0] = 0x00;
            l_arrH264Flag[1] = 0x00;    
            l_arrH264Flag[2] = 0x00;
            l_arrH264Flag[3] = 0x01;
            l_arrH264Flag[4] = 0x67;
            fwrite(l_arrH264Flag,1,5,g_WriteFd);
            ls_bWriteFlag = true;
            break;
        }
        
        default :
            break;
    }

    
    if((stRtpUnpack.stOutRTPPack.u32Paylen>0)&&(true==ls_bWriteFlag))
    {  
        //static unsigned int ls_u32Count = 0;
        //printf("u32SeqNum     :%d  ls_u32Count = %d  len = %d \n",stRtpUnpack.stOutRTPPack.u32SeqNum,ls_u32Count++,
        //    stRtpUnpack.stOutRTPPack.u32Paylen);
        fwrite(stRtpUnpack.stOutRTPPack.pu8Payload,1,stRtpUnpack.stOutRTPPack.u32Paylen,g_WriteFd);
    }
 
    return 0;
};

/******************************************************** 
Function: pstUnpackData	
Description: 根据RTP协议解析网络接收到的H264数据包
Input:	*pstUnpackData
OutPut: *pstUnpackData;
Return: 
    0:正常解包
    小于0:解析错误
    0xff:数据包错误
Others: 
    1.RTP传输的信息在这里解析提取
    2.分片包的帧类型在这里判断,并且在这里添加了h264的头
Author: Caibiao Lee
Date:	2019-10-05
*********************************************************/
static int RTP_Client_UnPackH264Stream(RTP_UNPACK_S *pstUnpackData)
{
    unsigned char *l_pu8InputDataAddr=NULL;
    unsigned int l_s32InputDataLen = 0;
    RTP_PACKET_S *l_pstRTPPack   = NULL;
    RTP_HEADER_S *l_pstRTPHeader = NULL;
    RTP_NALU_HEADER_S *l_pstNaluHeader = NULL;
    RTP_FU_HEADER_S *l_pstFUHeader     = NULL;
    RTP_FU_INDICATOR_S *l_pstFUIndicator = NULL;
 
    
    /**RTP 包头长度为12字节**/
    if((NULL==pstUnpackData)||(NULL==pstUnpackData->pu8InputDataAddr)||(pstUnpackData->u32InputDataLen<12))
    {
        printf("%s %d input para error \n",__FUNCTION__,__LINE__);
        return -1;
    };

    l_pstRTPPack   = (RTP_PACKET_S*)&pstUnpackData->stOutRTPPack;
    l_pstRTPHeader = (RTP_HEADER_S*)&pstUnpackData->pu8InputDataAddr[0];

    /**RTP 信息提取**/
    l_pstRTPPack->u8Version     = l_pstRTPHeader->bit1Version;
	l_pstRTPPack->u8Padding     = l_pstRTPHeader->bit1Padding;
	l_pstRTPPack->u8Extension   = l_pstRTPHeader->bit1Extension;
	l_pstRTPPack->u8Cc          = l_pstRTPHeader->bit4CsrcLen;
	l_pstRTPPack->u8Marker      = l_pstRTPHeader->bit1Marker;
	l_pstRTPPack->u8Pt          = l_pstRTPHeader->bit7PayLoadType;

	/**RTP *****/
    l_pstRTPPack->u32SeqNum = 0;
    l_pstRTPPack->u32SeqNum = (pstUnpackData->pu8InputDataAddr[2] & 0xff);
    l_pstRTPPack->u32SeqNum <<= 8;
    l_pstRTPPack->u32SeqNum |= (pstUnpackData->pu8InputDataAddr[3] & 0xff);

	/**RTP 时间戳**/
    l_pstRTPPack->u32TimeStamp = (pstUnpackData->pu8InputDataAddr[4] & 0xff);
    l_pstRTPPack->u32TimeStamp <<= 8;
    l_pstRTPPack->u32TimeStamp |= (pstUnpackData->pu8InputDataAddr[5] & 0xff);
    l_pstRTPPack->u32TimeStamp <<= 8;
    l_pstRTPPack->u32TimeStamp |= (pstUnpackData->pu8InputDataAddr[6] & 0xff);
    l_pstRTPPack->u32TimeStamp <<= 8;
    l_pstRTPPack->u32TimeStamp |= (pstUnpackData->pu8InputDataAddr[7] & 0xff);

	/**RTP 同步源ID**/
    l_pstRTPPack->u32Ssrc = (pstUnpackData->pu8InputDataAddr[8] & 0xff);
    l_pstRTPPack->u32Ssrc <<= 8;
    l_pstRTPPack->u32Ssrc |= (pstUnpackData->pu8InputDataAddr[9] & 0xff);
    l_pstRTPPack->u32Ssrc <<= 8;
    l_pstRTPPack->u32Ssrc |= (pstUnpackData->pu8InputDataAddr[10] & 0xff);
    l_pstRTPPack->u32Ssrc <<= 8;
    l_pstRTPPack->u32Ssrc |= (pstUnpackData->pu8InputDataAddr[11] & 0xff);
    
    l_pstNaluHeader = (RTP_NALU_HEADER_S*)&pstUnpackData->pu8InputDataAddr[12];

    pstUnpackData->u8OutNaluType = l_pstNaluHeader->bit5TYPE;

    /**开始解包数据**/
    if (0==l_pstNaluHeader->bit5TYPE)
	{
		printf("%s %d 这个包有错误,0无定义 \n",__FUNCTION__,__LINE__);
        return -2;
        
	}
    else if(0x06==l_pstNaluHeader->bit5TYPE)
    {
        /**H264视频帧的SEI**/
        pstUnpackData->eFrameType = H264_SEI;
        pstUnpackData->stOutRTPPack.pu8Payload = &pstUnpackData->pu8InputDataAddr[13];
        pstUnpackData->stOutRTPPack.u32Paylen = pstUnpackData->u32InputDataLen - 13;
        return 0;
        
    }else if(0x07==l_pstNaluHeader->bit5TYPE)
    {
        /**H264视频帧的SPS**/
        pstUnpackData->eFrameType = H264_SPS;
        pstUnpackData->stOutRTPPack.pu8Payload = &pstUnpackData->pu8InputDataAddr[13];
        pstUnpackData->stOutRTPPack.u32Paylen = pstUnpackData->u32InputDataLen - 13;

        return 0;
        
    }else if(0x08==l_pstNaluHeader->bit5TYPE)
    {
        /**H264视频帧的PPS**/
        pstUnpackData->eFrameType = H264_PPS;
        pstUnpackData->stOutRTPPack.pu8Payload = &pstUnpackData->pu8InputDataAddr[13];
        pstUnpackData->stOutRTPPack.u32Paylen = pstUnpackData->u32InputDataLen - 13;

        return 0;
        
    }
    else if (24==l_pstNaluHeader->bit5TYPE)                    
	{	
		/**STAP-A   单一时间的组合包**/
		printf("当前包为STAP-A\n");
        return 0xff;
        
	}else if (25==l_pstNaluHeader->bit5TYPE)                   
	{
        /**STAP-B   单一时间的组合包**/
        printf("当前包为STAP-B\n");
        return 0xff;
                
	}else if (26==l_pstNaluHeader->bit5TYPE)                    
	{
		/**MTAP16   多个时间的组合包**/
		printf("当前包为MTAP16\n");
        return 0xff;
        
	}else if (27==l_pstNaluHeader->bit5TYPE)                   
	{
		/**MTAP24   多个时间的组合包**/
		printf("当前包为MTAP24\n");
        return 0xff;
        
	}else if (28==l_pstNaluHeader->bit5TYPE)
    {
        unsigned char F;
        unsigned char NRI;
        unsigned char TYPE;
        unsigned char nh;
    
        /**FU-A分片包,解码顺序和传输顺序相同**/
        l_pstFUIndicator = (RTP_FU_INDICATOR_S *)&pstUnpackData->pu8InputDataAddr[12];
        l_pstFUHeader    = (RTP_FU_HEADER_S *)&pstUnpackData->pu8InputDataAddr[13];

        F    =  l_pstFUIndicator->BitF << 7;
        NRI  =  l_pstFUIndicator->BitNRI << 5;
        TYPE =  l_pstFUHeader->Bit5TYPE;    
        nh = F | NRI | TYPE;
        /**分片包最后一个包**/
        if(1==l_pstRTPHeader->bit1Marker)
        {
            pstUnpackData->eFrameType = FU_END_PACK;
            pstUnpackData->stOutRTPPack.pu8Payload = &pstUnpackData->pu8InputDataAddr[14];
            pstUnpackData->stOutRTPPack.u32Paylen = pstUnpackData->u32InputDataLen - 14;
            return 0;
            
        }else if(0==l_pstRTPHeader->bit1Marker)/**分片包 但不是最后一个包**/
        {
            if (1==l_pstFUHeader->Bit1S)/**分片的第一个包**/ 
            {
                pstUnpackData->eFrameType = FU_START_PACK;

                /**注意第一包需要添加帧类型**/
                pstUnpackData->pu8InputDataAddr[14-1] = nh;
                //printf("biao debug Falg = 0x%x \n",nh);
                pstUnpackData->stOutRTPPack.pu8Payload = &pstUnpackData->pu8InputDataAddr[14-1];
                /**多加了一个字节,这里需要修改长度,不然会花屏**/
                pstUnpackData->stOutRTPPack.u32Paylen = pstUnpackData->u32InputDataLen - 14 + 1;
                
                return 0;
                
            }else/**如果不是第一个包,也就是中间包**/
            {
                pstUnpackData->eFrameType = FU_MIDllE_PACK;
                pstUnpackData->stOutRTPPack.pu8Payload = &pstUnpackData->pu8InputDataAddr[14];
                pstUnpackData->stOutRTPPack.u32Paylen = pstUnpackData->u32InputDataLen - 14;

                return 0;
                
            }
        }
    }else if (29==l_pstNaluHeader->bit5TYPE)
    {
            /**FU-B分片包,解码顺序和传输顺序相同**/
            if (1==l_pstRTPHeader->bit1Marker)                  
            {   
                /**分片包最后一个包**/
                printf("当前包为FU-B分片包最后一个包\n");
            
            }
            else if (0==l_pstRTPHeader->bit1Marker)             
            {
                /**分片包 但不是最后一个包**/
                printf("当前包为FU-B分片包\n");
            }
    }else
    {
        printf("这个包有错误\n");
    }

    return 0xff;
}

/******************************************************** 
Function:    RTP_Client_GetOnePacketData  
Description: 获取RTP网络数据包,解析,并且存到文件中去
Input:  *pstRTPClient
OutPut: *pstRTPClient
Return: 0 成功,非0失败
Others: 
    1.该函数本是获取一个RTP网络包数据,为了测试,这里直接
      强制获取需要测试的包数之后再返回。
    2.RTP 网络数据包最大为1500 字节,所以接收缓存打下协议设置为1500
Author: Caibiao Lee
Date:   2019-10-05
*********************************************************/
int RTP_Client_GetOnePacketData(RTP_STATUS_S *pstRTPClient)
{
	int i = 0;
	int l_s32Ret = 0;
	int l_s32SocketFd = 0;
	unsigned char *l_pu8RcvBuf = NULL;
    RTP_UNPACK_S  l_stRtpUnpack = {0};
    RTP_UNPACK_S *l_pstRtpUnpack = &l_stRtpUnpack;

	if(NULL==pstRTPClient)
	{
		printf("%s %d input para error \n",__FUNCTION__,__LINE__);
		return -1;
	}
    
	l_s32SocketFd = pstRTPClient->s32RTPSockFd;

	if(0>=l_s32SocketFd)
	{
		printf("%s %d socket fd is close \n",__FUNCTION__,__LINE__);
		return -2;
	}

	l_pu8RcvBuf = (unsigned char*)malloc(RTP_RECV_DATA_LEN);
	if(NULL==l_pu8RcvBuf)
	{
		printf("%s %d malloc error \n",__FUNCTION__,__LINE__);
		return -3;
	}

    /**for debug**/
    if(NULL==g_WriteFd)
    {
        g_WriteFd = fopen("./data.h264","w+");
    };
    
    i=4000;
    while(i-->0)
    {
        bzero(l_pu8RcvBuf,RTP_RECV_DATA_LEN);
        l_s32Ret = NET_SocketRecvData(l_s32SocketFd,(void *)l_pu8RcvBuf,RTP_RECV_DATA_LEN); 
        if(l_s32Ret <= 0)
        {
            printf("%s %d :RTP Recv Data Error l_s32Ret = %d \n",__FUNCTION__,__LINE__,l_s32Ret);
            return -3;
        }

        l_pstRtpUnpack->pu8InputDataAddr = l_pu8RcvBuf;
        l_pstRtpUnpack->u32InputDataLen  = l_s32Ret;
        
        l_s32Ret = RTP_Client_UnPackH264Stream(l_pstRtpUnpack);
        if(0==l_s32Ret)
        {    
            RTP_Client_H264StreamStore(l_stRtpUnpack);

        }else if(0xff==l_s32Ret)
        {
            printf("%s %d Unknow data \n",__FUNCTION__,__LINE__);
        }else
        {
            printf("unpacket data error \n");
        }
    }
    
    return 0;
}

问题分析:

    对于网络视频流,客户端接收到视频数据,有可能出现显示不出来或是花屏的现象,问题一般定位方法有:

  •     查看H264 帧标签没有添加或是添加错误。
  •     查看接收到的RTP网络数据包,看网络包序号是否连续,是否有丢包,统计丢包率有多高。
  •     查看RTP网络数据包的时间戳,看时间戳是否正常增加。
  •     查看RTP连接的系统网络缓存有多大,看是否有数据因为缓存满了而导致数据丢失。   

下载路径:

上面测试程序工程下载路径:

     csnd 下载:    RtspRtcpRtpLoad_h264.tar.gz

    github: