上篇博文介绍了修复ffmpeg分离PS流的BUG,有同学关心定位BUG时抓网络RTP包分离HEVC码流的问题,本次重开一博文介绍此问题,并在结尾附上源代码。
一、rtpdump文件解析
使用tcpdump或wireshark抓取rtp网络包以后存为pcap文件,然后用wireshark导出位更简洁的rtpdump文件。方法如下:1)在wireshark中,鼠标右击其中一个网络包,在弹出菜单中选择“解码为”,将该网络包的UDP端口设置为RTP协议。2)依次选择菜单栏“电话”\“RTP”\“RTP流”,根据需要选定一个RTP流,然后点击下方的Export导出为rtpdump格式的文件,我们下面的分析都基于此文件。
rtpdump文件的格式参考:http://www.cs.columbia.edu/irt/software/rtptools/
rtpdump文件以"#!rtpplay1.0
addrss/port\n"为文件头起始,其后跟一个结构头:
typedef struct {
struct timeval32 {
uint32_t tv_sec; /* start of recording (GMT) (seconds) */
uint32_t tv_usec; /* start of recording (GMT) (microseconds)*/
} start;
uint32_t source; /* network source (multicast address) */
uint16_t port; /* UDP port */
uint16_t padding; /* padding */
} RD_hdr_t;
在上述文件头之后,就是一个个的rtp buffer,每个rtp buffer都有一个buffer头,buffer结构如下:
typedef struct {
uint16_t length; /* length of packet, including this header (may
be smaller than plen if not whole packet recorded) */
uint16_t plen; /* actual header+payload length for RTP, 0 for RTCP */
uint32_t offset; /* milliseconds since the start of recording */
} RD_packet_t;
typedef union {
struct {
RD_packet_t hdr;
char data[8000];
} p;
char byte[8192];
} RD_buffer_t;
RD_buffer_t中的data即为rtp数据。有了上述数据结构就可以写出一个解析rtpdump文件头的函数:
/*
* Read header. Return -1 if not valid, 0 if ok.
*/
int RD_header(FILE *in, struct sockaddr_in *sin, int verbose)
{
RD_hdr_t hdr;
time_t tt;
char line[80], magic[80];
if (fgets(line, sizeof(line), in) == NULL) return -1;
sprintf(magic, "#!rtpplay%s ", RTPFILE_VERSION);
if (strncmp(line, magic, strlen(magic)) != 0) return -1;
if (fread((char *)&hdr, sizeof(hdr), 1, in) == 0) return -1;
hdr.start.tv_sec = ntohl(hdr.start.tv_sec);
hdr.port = ntohs(hdr.port);
if (verbose) {
struct tm *tm;
struct in_addr in;
in.s_addr = hdr.source;
tt = (time_t)(hdr.start.tv_sec);
tm = localtime(&tt);
strftime(line, sizeof(line), "%c", tm);
printf("Start: %s\n", line);
printf("Source: %s (%d)\n", inet_ntoa(in), hdr.port);
}
if (sin && sin->sin_addr.s_addr == 0) {
sin->sin_addr.s_addr = hdr.source;
sin->sin_port = htons(hdr.port);
}
return 0;
} /* RD_header */
当然也会写出一个解析rtp buffer的函数:
int RD_read(FILE *in, RD_buffer_t *b)
{
/* read packet header from file */
if (fread((char *)b->byte, sizeof(b->p.hdr), 1, in) == 0) {
/* we are done */
return 0;
}
/* convert to host byte order */
b->p.hdr.length = ntohs(b->p.hdr.length) - sizeof(b->p.hdr);
b->p.hdr.offset = ntohl(b->p.hdr.offset);
b->p.hdr.plen = ntohs(b->p.hdr.plen);
/* read actual packet */
if (fread(b->p.data, b->p.hdr.length, 1, in) == 0) {
perror("fread body");
return 0;
}
return b->p.hdr.length;
} /* RD_read */
上述代码改自自rtptools。
二、从RTP中取出HEVC码流
HEVC的RTP打包规则来自:https://tools.ietf.org/html/draft-ietf-payload-rtp-h265-14
HEVC的NAL跟H264相比最大的区别是,HEVC NAL头有2个字节。由于HEVC PACI packets通常较少使用,所以HEVC的RTP解析一般要解决2个主要的问题:1)一个NALU由多个RTP包组成;2)一个RTP包含有多个NALU。除了这2个问题,其他的RTP包基本上在其RTP负载的前面加上00 00 00 01头即可。
1)一个NALU由多个RTP组成,即fragmentation unit,简称FU,它有一个字节的FU header。
/*
* decode the HEVC payload header according to section 4 of draft version 6:
*
* 0 1
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* |F| Type | LayerId | TID |
* +-------------+-----------------+
*
* Forbidden zero (F): 1 bit
* NAL unit type (Type): 6 bits
* NUH layer ID (LayerId): 6 bits
* NUH temporal ID plus 1 (TID): 3 bits
* decode the FU header
*
* 0 1 2 3 4 5 6 7
* +-+-+-+-+-+-+-+-+
* |S|E| FuType |
* +---------------+
*
* Start fragment (S): 1 bit
* End fragment (E): 1 bit
* FuType: 6 bits
*/
FU的NAL类型为49,一个HEVC帧如果长度超过最大RTP打包长度,则使用FU打包,FU头中有起始标记,S为开始标记,E为结束标记,SE都没有标记,则为中间标记,我们可以称为M FU。即一组FU包由1个S FU,N(>=0)个M FU,以及一个E FU组成,所以要根据FU的NAL头和FU头将一个序列的FU HEVC包,还原为一个真正的HEVC NALU。
/* fragmentation unit (FU) */
case 49:
/* pass the HEVC payload header (two bytes) */
buf += 2;
len -= 2;
/*
* decode the FU header
*
* 0 1 2 3 4 5 6 7
* +-+-+-+-+-+-+-+-+
* |S|E| FuType |
* +---------------+
*
* Start fragment (S): 1 bit
* End fragment (E): 1 bit
* FuType: 6 bits
*/
first_fragment = buf[0] & 0x80;
last_fragment = buf[0] & 0x40;
fu_type = buf[0] & 0x3f;
m_firstfrag = first_fragment;
m_lastfrag = last_fragment;
/* pass the HEVC FU header (one byte) */
buf += 1;
len -= 1;
//cout << "nal_type:" << nal_type << " FU type:" << fu_type << " first_frag:" << first_fragment << " last_frag:" << last_fragment << " with " << len <<" bytes\n";
/* sanity check for size of input packet: 1 byte payload at least */
if (len <= 0) {
return -1;
}
if (first_fragment && last_fragment) {
return -1;
}
ret = nal_type;
/*modify nal header*/
new_nal_header[0] = (rtp_pl[0] & 0x81) | (fu_type << 1);
new_nal_header[1] = rtp_pl[1];
handleFragPackage(buf, len, first_fragment,new_nal_header, sizeof(new_nal_header), outbuf,outlen);
break;
/*
* process one hevc frag package,add the startcode and nal header only when the package set start_bit
*/
void H265Frame::handleFragPackage(const uint8_t *buf, int len,int start_bit,const uint8_t *nal_header,int nal_header_len,uint8_t *outbuf,int *outlen){
int tot_len = len;
int pos = 0;
if (start_bit)
tot_len += sizeof(start_sequence) + nal_header_len;
if (start_bit) {
memcpy(outbuf + pos, start_sequence, sizeof(start_sequence));
pos += sizeof(start_sequence);
memcpy(outbuf + pos, nal_header, nal_header_len);
pos += nal_header_len;
}
memcpy(outbuf + pos, buf, len);
*outlen = tot_len;
}
2)一个RTP包含有N(N>=2)个NALU,即aggregated packet,简称AP,其NAL值为48。在HEVC的RTP打包中,一般将VPS、SPS、PPS以及SEI等NALU打入一个RTP包中。AP包的解析相对比较简单,将各部分拆开并分别增加00 00 00 01头即可。在HEVC的RTP解包中,AP因为包含了如此重要的参数,所以比较重要,AP包解错,HEVC解码将无法进行。
/* aggregated packet (AP) - with two or more NAL units */
case 48:
//cout << "nal_type:" << nal_type << "\n";
buf += 2;
len -= 2;
//handl aggregated p
if(handleAggregatedPacket(buf, len,outbuf,outlen) == 0){
ret = nal_type;
}
break;
/* aggregated packet (AP) - with two or more NAL units
* we add start_sequence_code_header for every NAL unit
*/
int H265Frame::handleAggregatedPacket(const uint8_t *buf, int len,uint8_t *outbuf, int *outlen)
{
int pass = 0;
int total_length = 0;
uint8_t *dst = NULL;
// first we are going to figure out the total size
for (pass = 0; pass < 2; pass++) {
const uint8_t *src = buf;
int src_len = len;
while (src_len > 2) {
uint16_t nal_size = AV_RB16(src);
// consume the length of the aggregate
src += 2;
src_len -= 2;
if (nal_size <= src_len) {
if (pass == 0) {
// counting
total_length += sizeof(start_sequence) + nal_size;
} else {
// copying
memcpy(dst, start_sequence, sizeof(start_sequence));
dst += sizeof(start_sequence);
memcpy(dst, src, nal_size);
dst += nal_size;
}
} else {
cout << "[HEVC] Aggregated error,nal size exceeds length: " << nal_size << " " << src_len << "\n";
return -1;
}
// eat what we handled
src += nal_size;
src_len -= nal_size;
}
if (pass == 0) {
dst = outbuf;
*outlen = total_length;
}
}
return 0;
}
上述RTP封包的HEVC解析代码改自ffmpeg。
上述源代码已整合在一个vs2012工程中,下载地址:http://download.csdn.net/download/andyshengjl/10252397