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

网络摄像头开发 同时被 3 个专栏收录
4 篇文章 10 订阅
4 篇文章 0 订阅
6 篇文章 1 订阅

前言:

    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属于传输层,如下图所示意。

   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网络数据包结构图如下:

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

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.     为方便其他地方使用,可以将视频数据按帧添加进队列。

代码实现:

   完整代码结构如下:

 这里贴出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连接的系统网络缓存有多大,看是否有数据因为缓存满了而导致数据丢失。   
  •     查看RTP数据流中是否同时传输了多个流,比如同时传输音频流和视频流。

下载路径:

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

     csnd 下载:    RtspRtcpRtpLoad_h264.tar.gz

    github: 

 

 

 

  • 3
    点赞
  • 5
    评论
  • 29
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

相关推荐
©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值