ONVIF网络摄像头(IPC)客户端开发—RTSP RTCP RTP加载H264数据
前言:
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;
设计思路:
- 通过ONVIF协议获取IPC网络摄像头RTSP的URL地址,为了简化测试程序,不在这里介绍,本文中使用固定地址:rtsp://192.168.0.120:554/cam/realmonitor?channel=1&subtype=0&unicast=true&proto=Onvif
- 通过RTSP协议发起数据流,同时获取服务端的RTCP和RTP的网络端口号,数据类型等信息。
- 建立RTCP,RTP连接,RTCP是TCP连接,RTP在这里走的是UDP连接。
- 客户端接收RTP网络数据包
- 对RTP客户端接收到的网络数据包进行解包
- 将解包之后的数据写入文件,对于H264视频帧的第一包数据,需要添加上H264帧头标签00 00 00 01 6X信息。
- 为方便其他地方使用,可以将视频数据按帧添加进队列。
代码实现:
完整代码结构如下:
这里贴出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: