文章目录
一,前言
本文旨在从传输h264编码的RTP数据包中,解析出h264裸流数据。对于传输h264数据的rtp数据包,一般有三种类型,单包、单一时间的组合包(STAP-A)、FU-A分片包。本文提取出这三种类型的数据,保存到文件中,得到的h264裸流文件可以通过ffmpeg播放。
二,RTP 数据包格式
2.1 RTP 包头
V:版本号,一般为2,2bits
P:填充,1bits
X:扩展,1bits
CC:CSRC计数,4bits
M:标志,1bits
PT:负载类型,7bits。对于H264一般值为100
sequence number:序列号,16bits
timestamp:时间戳,32bits
SSRC:同步源标志,32bits
CSRC:一般不用。所以,RTP数据包头一般为12个字节(96bits)。
通过wireshark抓包可以看到如下rtp 包头信息
2.2 RTP负载
rtp负载就是传输的h264的裸流数据,现在要做的就是要从这些负载中还原出h264的裸流数据。
三,RTP包负载中H264数据格式
RTP包传输中的H264数据,由NALU Header+NALU Payload组成。对于NALU Header,单包、组合包的NALU Header和分片包的NALU Header又有所不同。单包、分片包的NALU Payload和组合包的负载又有所不同。
3.1 RTP包中的H264数据类型
NAl type | NALU 单元类型 | |
---|---|---|
1-23 | NAL单元 | 单个NALU包 |
24 | STAP-A | 单一时间的组合包 |
28 | FU-A | 分片的单元 |
3.2 NALU单元的类型
1 | 非IDR图像中不采用数据划分的片段 |
---|---|
2 | 非IDR图像中A类数据划分片段 |
3 | 非IDR图像中B类数据划分片段 |
4 | 非IDR图像中C类数据划分片段 |
5 | IDR图像的片段 |
6 | 补充增强信息 (SEI) |
7 | 序列参数集(sps) |
8 | 图像参数集(PPS) |
9 | 分割符 |
10 | 序列结束符 |
11 | 流结束符 |
12 | 填充数据 |
3.3 单包和组合包的NALU Header
一个RTP包中去掉上面12个字节的包头,第十三个字节就是NALU Header,主要看它的后五位,“00111”表示该H264数据的类型,也就是该NALU单元的类型。
3.4 分片包的NALU Header
分片包的NALU Header由FU identifier+FU Header组成。一个RTP包中去掉上面12个字节的包头,第十三个字节就是FU identifier,后五位“00111”表示该H264数据包的类型。第十四个字节就是FU Header,其后五位“00001”表示NALU单元的类型。最终由FU identifier的前三位,和FU Header的后五位组成写入裸流文件中的一个NALU Header。 分片包又有,起始包、中间包和结束包。可以由RTP头中的M标志判断该包是否为结束包,M为1表示结束包,M为0表示可能为起始包或中间包,再由FU Header的第七位(0位起算)判断其是否为起始包。也可以通过FU Header的第七、第六位(0位起算)判断,10为起始包,00为中间包,01为结束包。
3.5 单包和分片包的NALU Payload
单包的NALU Payload就是这个NALU单元的数据。多个分片包的Payload共同组成一个NALU单元的数据。
3.6 组合包的NALU Payload
组合包中存在这多个NALU单元数据,需要分别提取出来,加上起始码“00 00 00 01”组成一个个NALU单元写入文件。
比如有这么一段RTP包,“80 64 94 1d 00 04 e3 3c 9d 9c 66 fc”为RTP包头。78为NAL Header,其后五位“11000”= 24表示该包为组合包。“00 0f”表示第一个NALU单元的长度(NALU header+payload),“67 42 c0 29 43 23 50 16 87 a4 03 c2 21 1a 80” ,67为NALU header,其后五位“00111”表示该NALU单元为sps类型。"00 04"表示第二个NALU单元的长度(NALU header+payload)“68 48 e3 c8”68为NALU header,其后五位“01000”表示该NALU单元为PPS类型。
/*
80 64 94 1d 00 04 e3 3c 9d 9c 66 fc
78 00 0f 67 42 c0 29 43 23 50 16 87
a4 03 c2 21 1a 80 00 04 68 48 e3 c8
*/
四,文件中的H264数据格式
对于h264裸流文件,其由一个个的NALU单元组成,NALU单元由00 00 00 01的起始码+NALU Header+NALU数据组成。
五,解包生成h264裸流文件程序
最后生成的receive.h264文件,直接使用ffmplay receive.h264播放即可。
#define PARESERTP
#define TEST_PARESERTP
#ifdef TEST_PARESERTP
static FILE* poutfile = NULL;
const char* outputfilename = "G:\\receive.h264";
int initTest()
{
poutfile = fopen(outputfilename, "ab+");
if (!poutfile)
{
return -1;
}
return 0;
}
void uninitTest()
{
if (poutfile)
{
fclose(poutfile);
poutfile = NULL;
}
}
#endif
#ifdef PARESERTP
#define DC_PRINT_DEBUG qDebug() << __FUNCTION__ << __LINE__
#define MAXDATASIZE 2048
typedef struct
{
unsigned char version; //!< Version, 2 bits, MUST be 0x2
unsigned char padding; //!< Padding bit, Padding MUST NOT be used
unsigned char extension; //!< Extension, MUST be zero
unsigned char cc; //!< CSRC count, normally 0 in the absence of RTP mixers
unsigned char marker; //!< Marker bit
unsigned char pt; //!< 7 bits, Payload Type, dynamically established
unsigned int seq_no; //!< RTP sequence number, incremented by one for each sent packet
unsigned int timestamp; //!< timestamp, 27 MHz for H.264
unsigned int ssrc; //!< Synchronization Source, chosen randomly
unsigned char* payload; //!< the payload including payload headers
unsigned int paylen; //!< length of payload in bytes
} RTPpacket_t;
typedef struct
{
/* byte 0 */
unsigned short csrc_len : 4; /* expect 0 */
unsigned short extension : 1; /* expect 1, see RTP_OP below */
unsigned short padding : 1; /* expect 0 */
unsigned short version : 2; /* expect 2 */
/* byte 1 */
unsigned short payloadtype : 7; /* RTP_PAYLOAD_RTSP */
unsigned short marker : 1; /* expect 1 */
/* bytes 2,3 */
unsigned short seq_no;
/* bytes 4-7 */
unsigned int timestamp;
/* bytes 8-11 */
unsigned int ssrc; /* stream number is used here. */
} RTP_FIXED_HEADER;
typedef struct
{
unsigned char forbidden_bit; //! Should always be FALSE
unsigned char nal_reference_idc; //! NALU_PRIORITY_xxxx
unsigned char nal_unit_type; //! NALU_TYPE_xxxx
unsigned int startcodeprefix_len; //! 前缀字节数
unsigned int len; //! 包含nal 头的nal 长度,从第一个00000001到下一个000000001的长度
unsigned int max_size; //! 做多一个nal 的长度
unsigned char* buf; //! 包含nal 头的nal 数据
unsigned int lost_packets; //! 预留
} NALU_t;
typedef struct
{
//byte 0
unsigned char TYPE : 5;
unsigned char NRI : 2;
unsigned char F : 1;
} NALU_HEADER; // 1 BYTE
typedef struct
{
//byte 0
unsigned char TYPE : 5;
unsigned char NRI : 2;
unsigned char F : 1;
} FU_INDICATOR; // 1 BYTE
typedef struct
{
//byte 0
unsigned char TYPE : 5;
unsigned char R : 1;
unsigned char E : 1;
unsigned char S : 1;
} FU_HEADER; // 1 BYTES
NALU_t* AllocNALU(int buffersize)
{
NALU_t* n;
if ((n = (NALU_t*)calloc(1, sizeof(NALU_t))) == NULL)
{
//DC_PRINT_DEBUG<<Str.sprintf("AllocNALU Error: Allocate Meory To NALU_t Failed ");
exit(0);
}
return n;
}
void FreeNALU(NALU_t* n)
{
if (n)
{
free(n);
}
}
void rtp_unpackage(char* bufIn, int len, BYTE* outbuff, unsigned int* dataLen)
{
unsigned char recvbuf[1500] = {
0 };
RTPpacket_t* p = NULL;
RTP_FIXED_HEADER* rtp_hdr = NULL;
NALU_HEADER* nalu_hdr = NULL;
FU_INDICATOR* fu_ind = NULL;
FU_HEADER* fu_hdr = NULL;
int total_bytes = 0;
static int total_recved = 0;
QString Str;
static BYTE byHeadLong[] = {
0x00, 0x00, 0x00, 0x01 };
int offest = 0;
memcpy(recvbuf, bufIn, len); //复制rtp包
//begin rtp_payload and rtp_header
if ((p = (RTPpacket_t*)malloc(sizeof(RTPpacket_t))) == NULL)
{
DC_PRINT_DEBUG << Str.sprintf("RTPpacket_t MMEMORY ERROR\n");
}
if ((p->payload = (unsigned char*)malloc(MAXDATASIZE)) == NULL)
{
DC_PRINT_DEBUG << Str.sprintf("RTPpacket_t payload MMEMORY ERROR\n");
}
if ((rtp_hdr = (RTP_FIXED_HEADER*)malloc(sizeof(RTP_FIXED_HEADER))) == NULL)
{
DC_PRINT_DEBUG << Str.sprintf("RTP_FIXED_HEADER MEMORY ERROR\n");
}
// rtp_hdr =(RTP_FIXED_HEADER*)&recvbuf[0];
memcpy((void*)rtp_hdr, (void*)&recvbuf[0], sizeof(RTP_FIXED_HEADER));
p->version = rtp_hdr->version;
p->padding = rtp_hdr->padding;
p->extension = rtp_hdr->extension;
p->cc = rtp_hdr->csrc_len;
p->marker = rtp_hdr->marker;
p->pt = rtp_hdr->payloadtype;
p->seq_no = rtp_hdr->seq_no;
p->timestamp = rtp_hdr->timestamp;
p->ssrc = rtp_hdr->ssrc;
// DC_PRINT_DEBUG << Str.sprintf("my rtp decode versoin: %d,payload type: 0x%x,seq number: 0x%x,timestamp: 0x%x,ssrc: 0x%x\n",
// rtp_hdr->version, rtp_hdr->payloadtype, rtp_hdr->seq_no, rtp_hdr->timestamp, rtp_hdr->ssrc);
//end rtp_payload and rtp_header
//
//begin nal_hdr
if ((nalu_hdr = (NALU_HEADER*)malloc(sizeof(NALU_HEADER))) == NULL)
{
DC_PRINT_DEBUG << Str.sprintf("NALU_HEADER MEMORY ERROR\n");
}
memcpy((void*)nalu_hdr, (void*)&recvbuf[12], 1);
// DC_PRINT_DEBUG << Str.sprintf("forbidden_zero_bit: %d\n", nalu_hdr->F);
// DC_PRINT_DEBUG << Str.sprintf("nal_reference_idc: %d\n", nalu_hdr->NRI);
// DC_PRINT_DEBUG << Str.sprintf("nal type: %d\n", nalu_hdr->TYPE);
//end nal_hdr
//开始解包 对于h264 CB 有三种类型,单包、STAP-A组合包以及FU-A分包
if (nalu_hdr->TYPE == 0)
{
DC_PRINT_DEBUG << Str.sprintf("pkt error no 0 type\n");
}
else if (nalu_hdr->TYPE > 0 && nalu_hdr->TYPE < 24) //单包 0x00 < type < 0x18
{
DC_PRINT_DEBUG << Str.sprintf("cur pkt is singal\n");
#ifdef TEST_PARESERTP
putc(0x00, poutfile);
putc(0x00, poutfile);
putc(0x00, poutfile);
putc(0x01, poutfile); //写进起始字节0x00000001
#endif
memcpy(outbuff + total_bytes, byHeadLong, sizeof(byHeadLong));
total_bytes += sizeof(byHeadLong);
#ifdef TEST_PARESERTP
if(poutfile)
fwrite(nalu_hdr, 1, 1, poutfile); //写NAL_HEADER
#endif
memcpy(outbuff + total_bytes, nalu_hdr, sizeof(NALU_HEADER));
total_bytes += sizeof(NALU_HEADER);
memcpy(p->payload, &recvbuf[13], len - 13);
p->paylen = len - 13;
#ifdef TEST_PARESERTP
if(poutfile)
fwrite(p->payload, 1, p->paylen, poutfile); //写NAL数据
#endif
memcpy(outbuff + total_bytes, p->payload, len - 13);
total_bytes += p->paylen;
}
else if (nalu_hdr->TYPE == 24) //STAP-A 单一时间的组合包 0x18
{
int pktLen = len - 13;
/*
80 64 94 1d 00 04 e3 3c 9d 9c 66 fc
78 00 0f 67 42 c0 29 43 23 50 16 87
a4 03 c2 21 1a 80 00 04 68 48 e3 c8
*/
unsigned short naluSize;
unsigned int nextNaluSizeIndex = 13;
while (pktLen)
{
naluSize = recvbuf[nextNaluSizeIndex] << 8 | recvbuf[nextNaluSizeIndex + 1];
#ifdef TEST_PARESERTP
putc(0x00, poutfile);
putc(0x00, poutfile);
putc(0x00, poutfile);
putc(0x01, poutfile);
#endif
memcpy(outbuff + total_bytes, byHeadLong, sizeof(byHeadLong));
total_bytes += sizeof(byHeadLong);
#ifdef TEST_PARESERTP
if(poutfile)
fwrite(&recvbuf[nextNaluSizeIndex + 2], naluSize, 1, poutfile);
#endif
memcpy(outbuff + total_bytes, &recvbuf[nextNaluSizeIndex + 2], naluSize);
total_bytes += naluSize;
nextNaluSizeIndex = nextNaluSizeIndex + naluSize + 2;
pktLen = pktLen - naluSize - 2;
}
}
else if (nalu_hdr->TYPE == 28 || nalu_hdr->TYPE == 29) //FU-A、FU-B分片包
{
if ((fu_ind = (FU_INDICATOR*)malloc(sizeof(FU_INDICATOR))) == NULL)
{
DC_PRINT_DEBUG << Str.sprintf("FU_INDICATOR MEMORY ERROR\n");
}
if ((fu_hdr = (FU_HEADER*)malloc(sizeof(FU_HEADER))) == NULL)
{
DC_PRINT_DEBUG << Str.sprintf("FU_HEADER MEMORY ERROR\n");
}
// fu_ind=(FU_INDICATOR*)&recvbuf[12]; //分片包用的是FU_INDICATOR而不是NALU_HEADER
memcpy((void*)fu_ind, (void*)&recvbuf[12], 1); // 0x7c
// DC_PRINT_DEBUG << Str.sprintf("FU_INDICATOR->F :%d\n", fu_ind->F);
// DC_PRINT_DEBUG << Str.sprintf("FU_INDICATOR->NRI :%d\n", fu_ind->NRI);
// DC_PRINT_DEBUG << Str.sprintf("FU_INDICATOR->TYPE :%d\n", fu_ind->TYPE);
// fu_hdr=(FU_HEADER*)&recvbuf[13]; //FU_HEADER赋值
memcpy((void*)fu_hdr, (void*)&recvbuf[13], 1); //0x85
// DC_PRINT_DEBUG << Str.sprintf("FU_HEADER->S :%d\n", fu_hdr->S);
// DC_PRINT_DEBUG << Str.sprintf("FU_HEADER->E :%d\n", fu_hdr->E);
// DC_PRINT_DEBUG << Str.sprintf("FU_HEADER->R :%d\n", fu_hdr->R);
// DC_PRINT_DEBUG << Str.sprintf("FU_HEADER->TYPE :%d\n", fu_hdr->TYPE);
if (rtp_hdr->marker == 1) //分片包最后一个包
{
DC_PRINT_DEBUG << Str.sprintf("cur pkt is FU-A laster pkt\n");
memcpy(p->payload, &recvbuf[14], len - 14);
p->paylen = len - 14;
#ifdef TEST_PARESERTP
if(poutfile)
fwrite(p->payload, 1, p->paylen, poutfile); //写NAL数据
#endif
memcpy(outbuff + total_bytes, p->payload, p->paylen);
total_bytes += p->paylen;
DC_PRINT_DEBUG << Str.sprintf("payload len = %d\n", p->paylen);
// 保存所有的fu-a分包的数据到内存
memcpy(fuData + fuDataLen, outbuff, total_bytes);
fuDataLen += total_bytes;
fuReviceEnd = 1; // 接收到所有的FU分包数据
}
else if (rtp_hdr->marker == 0) //分片包 但不是最后一个包
{
if (fu_hdr->S == 1) //分片的第一个包
{
unsigned char F;
unsigned char NRI;
unsigned char TYPE;
unsigned char nh;
DC_PRINT_DEBUG << Str.sprintf("cur pkt is FU-A first pkt\n");
#ifdef TEST_PARESERTP
putc(0x00, poutfile);
putc(0x00, poutfile);
putc(0x00, poutfile);
putc(0x01, poutfile);
#endif
memcpy(outbuff + total_bytes, byHeadLong, sizeof(byHeadLong));
total_bytes += sizeof(byHeadLong);
F = fu_ind->F << 7;
NRI = fu_ind->NRI << 5;
TYPE = fu_hdr->TYPE;
nh = F | NRI | TYPE; //NAL HEADER
#ifdef TEST_PARESERTP
putc(nh, poutfile);
#endif
memcpy(outbuff + total_bytes, &nh, 1);
total_bytes += 1;
memcpy(p->payload, &recvbuf[14], len - 14);
p->paylen = len - 14;
#ifdef TEST_PARESERTP
if(poutfile)
fwrite(p->payload, 1, p->paylen, poutfile);
#endif
memcpy(outbuff + total_bytes, p->payload, p->paylen);
total_bytes += p->paylen;
// DC_PRINT_DEBUG << Str.sprintf("payload len = %d\n", p->paylen);
// 保存所有的fu-a分包的数据到内存
memset(fuData, 0, MAXFREAMLEN);
isFU = 1; //接收到第一个分包
memcpy(fuData + fuDataLen, outbuff, total_bytes);
fuDataLen += total_bytes;
}
else //分片的中间包
{
memcpy(p->payload, &recvbuf[14], len - 14);
p->paylen = len - 14;
#ifdef TEST_PARESERTP
if(poutfile)
fwrite(p->payload, 1, p->paylen, poutfile);
#endif
memcpy(outbuff + total_bytes, p->payload, p->paylen);
total_bytes += p->paylen;
// DC_PRINT_DEBUG << Str.sprintf("payload len = %d\n", p->paylen);
// 保存所有的fu-a分包的数据到内存
memcpy(fuData + fuDataLen, outbuff, total_bytes);
fuDataLen += total_bytes;
}
}
}
else
{
DC_PRINT_DEBUG << Str.sprintf("this pkt is error 30-31 no define\n");
}
// DC_PRINT_DEBUG<<"rtp decode total_bytes: "<<total_bytes;
*dataLen = total_bytes;
total_bytes = 0;
memset(recvbuf, 0, 1500);
free(fu_ind);
free(fu_hdr);
free(nalu_hdr);
free(rtp_hdr);
free(p->payload);
free(p);
//结束解包
//
return;
}
#endif