2023年7月23日发(作者:)
从⽹络rtp封包中分离hevch265 上篇博⽂介绍了修复ffmpeg分离PS流的BUG,有同学关⼼定位BUG时抓⽹络RTP包分离HEVC码流的问题,本次重开⼀博⽂介绍此问题,并在结尾附上源代码。⼀、rtpdump⽂件解析 使⽤tcpdump或wireshark抓取rtp⽹络包以后存为pcap⽂件,然后⽤wireshark导出位更简洁的rtpdump⽂件。⽅法如下:1)在wireshark中,⿏标右击其中⼀个⽹络包,在弹出菜单中选择“解码为”,将该⽹络包的UDP端⼝设置为RTP协议。2)依次选择菜单栏“电话”“RTP”“RTP流”,根据需要选定⼀个RTP流,然后点击下⽅的Export导出为rtpdump格式的⽂件,我们下⾯的分析都基于此⽂件。 rtpdump⽂件以"#!rtpplay1.0
addrss/portn"为⽂件头起始,其后跟⼀个结构头: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; _sec = ntohl(_sec); = ntohs(); if (verbose) { struct tm *tm; struct in_addr in; in.s_addr = ; tt = (time_t)(_sec); tm = localtime(&tt); strftime(line, sizeof(line), "%c", tm); printf("Start: %sn", line); printf("Source: %s (%d)n", inet_ntoa(in), ); } if (sin && sin->sin_addr.s_addr == 0) { sin->sin_addr.s_addr = ; sin->sin_port = htons(); } 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->), 1, in) == 0) { /* we are done */ return 0; } /* convert to host byte order */ b-> = ntohs(b->) - sizeof(b->); b-> = ntohl(b->); b-> = ntohs(b->); /* read actual packet */ if (fread(b->, b->, 1, in) == 0) { perror("fread body"); return 0;
}
return b->;
} /* RD_read */
上述代码改⾃⾃rtptools。
⼆、从RTP中取出HEVC码流 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 <<" bytesn"; /* 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。
发布者:admin,转转请注明出处:http://www.yc00.com/web/1690106527a306301.html
评论列表(0条)