从网络rtp封包中分离hevc/h265

    上篇博文介绍了修复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

你可能感兴趣的:(从网络rtp封包中分离hevc/h265)