一只海星的主页

RTSP拉流h265(hevc)+AAC关键节点详解!史上最全RTSP+hevc 交互全记录!

背景

8012年来了,H265已经跳出小众,日趋流行!在视频大数据时代,高压缩优势也越来越明显!
所以!搞一波RTSP+HEVC+AAC取流支持!

网上资料,h264比较齐全,对h265的说明比较零散,记录一下所有关键点!

H265原始码流处理

所有的视频帧(VPS/SPS/PPS/I/P)请去掉起始码0001,RTP包不需要起始码!
这里为什么单独拉出来呢,因为我就踩了一个坑,我大意啊,没有闪!上次搞RTSP很多年了,忘了这一茬,所以出现了很多莫名其妙的错误,后面单独开一篇!

H264与H265区别

首先HEVC在H264的图像描述上由PPS和SPS增加了VPS,这三者之间的关系如图:

RTSP协议H264与H265的区别

首先,我们来复习一下RTSP交互流程:
OPTION–>DESCRIBE–>SETUP–>PLAY–>PAUSE–>TEARDOWN
其中最大的区别就是在第二步DESCRIBE上,两者的SDP描述信息不同!

H264

a=rtpmap:96 H264/90000

H265

a=rtpmap:96 H265/90000
就这???

这样不能说不行,因为万能的VLC/ffplay就可以播!Robust杠杠的!
但碰到比如ijkplayer等别的播放器就over了!
还有就是,延时也比较高!
这里的主要原因其实就是SDP描述不全导致
因为没有在SDP多媒体描述文件中带上VPS/SPS/PPS参数,RTSP播放器接收解码的时候就要从流里面去遍历一遍取到所有信息,也就是延时增大的主要原因!

HEVC SDP文件

主要参考本文的核心文档《RTP Payload Format for H.265HEVC Video.txt
7.2.1 Mapping of Payload Type Parameters to SDP
我们看里面的sdp sample:

m=video 49170 RTP/AVP 98
a=rtpmap:98 H265/90000
a=fmtp:98 profile-id=1;sprop-vps= (video parameter sets data)

详细解释请点击章节名
这里我们就能看出,少了fmtp可选描述信息里面的VPS等相关视频描述参数,也就是上个章节介绍的HEVC的主要区别!
注意:VPS、SPS、PPS需要使用base64编码格式!
以下是wireshark抓完整的一次RTSP交互过程供参考

OPTIONS rtsp://192.168.10.122:554/live0 RTSP/1.0
CSeq: 2
User-Agent: LibVLC/3.0.11 (LIVE555 Streaming Media v2016.11.28)

RTSP/1.0 200 OK
CSeq: 2
Public: OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY

DESCRIBE rtsp://192.168.10.122:554/live0 RTSP/1.0
CSeq: 3
User-Agent: LibVLC/3.0.11 (LIVE555 Streaming Media v2016.11.28)
Accept: application/sdp

RTSP/1.0 200 OK
CSeq: 3
Content-Length: 266
Content-Type: application/sdp

v=0
o=- 91607499504 1 IN IP4 192.168.10.122
t=0 0
a=control:*
m=video 0 RTP/AVP 96
a=rtpmap:96 H265/90000
a=fmtp:96 sprop-vps=QAEMAf//AWAAAAMAsAAAAwAAAwBdrAk=;sprop-sps=RAHA8vA7NA==;sprop-pps=QgEBAWAAAAMAsAAAAwAAAwBdoAKAgC0WNrkkUvTcBAQEAg==
a=control:track0
SETUP rtsp://192.168.10.122:554/live0/track0 RTSP/1.0
CSeq: 4
User-Agent: LibVLC/3.0.11 (LIVE555 Streaming Media v2016.11.28)
Transport: RTP/AVP;unicast;client_port=58908-58909

RTSP/1.0 200 OK
CSeq: 4
Transport: RTP/AVP;unicast;client_port=58908-58909;server_port=36560-36561
Session: 3680

PLAY rtsp://192.168.10.122:554/live0 RTSP/1.0
CSeq: 5
User-Agent: LibVLC/3.0.11 (LIVE555 Streaming Media v2016.11.28)
Session: 3680
Range: npt=0.000-

RTSP/1.0 200 OK
CSeq: 5
Range: npt=0.000-
Session: 3680; timeout=60

TEARDOWN rtsp://192.168.10.122:554/live0 RTSP/1.0
CSeq: 6
User-Agent: LibVLC/3.0.11 (LIVE555 Streaming Media v2016.11.28)
Session: 3680

RTSP/1.0 200 OK
CSeq: 6
Session: 3680

ffmpeg里面(libavformat/rtpdec_hevc.c)的fmtp解封装过程

static av_cold int hevc_sdp_parse_fmtp_config(AVFormatContext *s,
                                              AVStream *stream,
                                              PayloadContext *hevc_data,
                                              const char *attr, const char *value)
{
    /* profile-space: 0-3 */
    /* profile-id: 0-31 */
    if (!strcmp(attr, "profile-id")) {
        hevc_data->profile_id = atoi(value);
        av_log(s, AV_LOG_TRACE, "SDP: found profile-id: %d\n", hevc_data->profile_id);
    }

    /* tier-flag: 0-1 */
    /* level-id: 0-255 */
    /* interop-constraints: [base16] */
    /* profile-compatibility-indicator: [base16] */
    /* sprop-sub-layer-id: 0-6, defines highest possible value for TID, default: 6 */
    /* recv-sub-layer-id: 0-6 */
    /* max-recv-level-id: 0-255 */
    /* tx-mode: MSM,SSM */
    /* sprop-vps: [base64] */
    /* sprop-sps: [base64] */
    /* sprop-pps: [base64] */
    /* sprop-sei: [base64] */
    if (!strcmp(attr, "sprop-vps") || !strcmp(attr, "sprop-sps") ||
        !strcmp(attr, "sprop-pps") || !strcmp(attr, "sprop-sei")) {
        uint8_t **data_ptr = NULL;
        int *size_ptr = NULL;
        if (!strcmp(attr, "sprop-vps")) {
            data_ptr = &hevc_data->vps;
            size_ptr = &hevc_data->vps_size;
        } else if (!strcmp(attr, "sprop-sps")) {
            data_ptr = &hevc_data->sps;
            size_ptr = &hevc_data->sps_size;
        } else if (!strcmp(attr, "sprop-pps")) {
            data_ptr = &hevc_data->pps;
            size_ptr = &hevc_data->pps_size;
        } else if (!strcmp(attr, "sprop-sei")) {
            data_ptr = &hevc_data->sei;
            size_ptr = &hevc_data->sei_size;
        } else
            av_assert0(0);

        ff_h264_parse_sprop_parameter_sets(s, data_ptr,
                                           size_ptr, value);
    }

    /* max-lsr, max-lps, max-cpb, max-dpb, max-br, max-tr, max-tc */
    /* max-fps */

    /* sprop-max-don-diff: 0-32767

         When the RTP stream depends on one or more other RTP
         streams (in this case tx-mode MUST be equal to "MSM" and
         MSM is in use), this parameter MUST be present and the
         value MUST be greater than 0.
    */
    if (!strcmp(attr, "sprop-max-don-diff")) {
        if (atoi(value) > 0)
            hevc_data->using_donl_field = 1;
        av_log(s, AV_LOG_TRACE, "Found sprop-max-don-diff in SDP, DON field usage is: %d\n",
                hevc_data->using_donl_field);
    }

    /* sprop-depack-buf-nalus: 0-32767 */
    if (!strcmp(attr, "sprop-depack-buf-nalus")) {
        if (atoi(value) > 0)
            hevc_data->using_donl_field = 1;
        av_log(s, AV_LOG_TRACE, "Found sprop-depack-buf-nalus in SDP, DON field usage is: %d\n",
                hevc_data->using_donl_field);
    }

    /* sprop-depack-buf-bytes: 0-4294967295 */
    /* depack-buf-cap */
    /* sprop-segmentation-id: 0-3 */
    /* sprop-spatial-segmentation-idc: [base16] */
    /* dec-parallel-ca: */
    /* include-dph */

    return 0;
}

HEVC RTP封包

这里主要介绍H265的RTP包怎么封。
先复习下基础知识

HEVC Nal Unit Header


– F: 1位
禁止的零位。在[HEVC]中要求为零。
– Type:6位
此字段将NAL单元类型
– LayerID、TID
LayerID表示NAL所在的Access unit所属的层,该字段是为了HEVC的继续扩展设置。TID确定了NAL所在的unit的时域上的层次

RTP封包

为什么需要知道NAL头结构体呢,因为RTP的封包格式是将所有的Nal包分成PAYLOAD_SIZE大小的包;PAYLOAD_SIZE=1420 //1460 1500-20-12-8,这里会有两种情况:
1. Nal包小于PAYLOAD_SIZE,单包直接发
4.4.1 Single NAL Unit Packets

这个时候码流直接拷贝就行了,PayloadHdr=Nal
参考FFmpeg里(libavformat/rtpenc_h264_hevc.c)的实现代码

         /* send it as one single NAL unit? */
         if (len <= rtp_ctx->max_payload_size) //小于对定的最大值时,直接发送(最大值一般小于mtu)
     {
         /* use the original NAL unit buffer and transmit it as RTP payload */
        ff_rtp_send_data(ctx, buf, len, last_packet_of_frame);

    }
  1. Nal包大于PAYLOAD_SIZE,需要分片多个RTP FU payload包
    4.4.3 Fragmentation Units (FUs)

– PayloadHdr = 49
– FU header S=Start E=END 第一片就是S=1 这样来赋值
– FU header FuType=NalType

FU Header 实现代码:

else {
            uint8_t nal_type = (buf[0] >> 1) & 0x3F;
            /*
             * create the HEVC payload header and transmit the buffer as fragmentation units (FU)
             *
             *    0                   1
             *    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
             *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
             *   |F|   Type    |  LayerId  | TID |
             *   +-------------+-----------------+
             *
             *      F       = 0
             *      Type    = 49 (fragmentation unit (FU))
             *      LayerId = 0
             *      TID     = 1
             */
            s->buf[0] = 49 << 1;
            s->buf[1] = 1;

            /*
             *     create the FU header
             *
             *     0 1 2 3 4 5 6 7
             *    +-+-+-+-+-+-+-+-+
             *    |S|E|  FuType   |
             *    +---------------+
             *
             *       S       = variable
             *       E       = variable
             *       FuType  = NAL unit type
             */
            s->buf[2]  = nal_type;
            /* set the S bit: mark as start fragment */
            s->buf[2] |= 1 << 7;

            /* pass the original NAL header */
            buf  += 2;
            size -= 2;

            flag_byte   = 2;
            header_size = 3;
        }

        while (size + header_size > s->max_payload_size) {
            memcpy(&s->buf[header_size], buf, s->max_payload_size - header_size);
            ff_rtp_send_data(s1, s->buf, s->max_payload_size, 0);
            buf  += s->max_payload_size - header_size;
            size -= s->max_payload_size - header_size;
            s->buf[flag_byte] &= ~(1 << 7);
        }
        s->buf[flag_byte] |= 1 << 6;
        memcpy(&s->buf[header_size], buf, size);
        ff_rtp_send_data(s1, s->buf, size + header_size, last);
    }

参考

发表评论

邮箱地址不会被公开。 必填项已用*标注