准备知识

H265 NALU类型简单介绍:

 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |

| F | Nalu Type            | LayerID                |  TID      |

F:必须为0,表示有效;为1的话表示无效

Nalu Type:6-bits NALType 确定NAL的类型,其中VCL NAL和non-VCL NAL各有32类。0-31是vcl nal单元;32-63,是非vcl nal单元。VCL是指携带编码数据的数据流,而non-VCL则是控制数据流

LayerID:表示NAL所在的Access unit所属的层,该字段是为了HEVC的继续扩展设置。也就是目前都是0,以后的扩展可能会用到。

TID:3 bits

通常情况下,F为0,LayerID为0,TID为1

注意:(1)H265 NALU的帧头信息占用两个字节(区别H264帧头信息占一个字节),且类型type占6位而不是5位,即第一个字节的1-6位(首位从0开始),解析方法:int type = (buf[0] & 0x7e) >> 1;

(2) 参数集(或者是其他信息)一共有四种而不是两种,分别是VPS,SPS,PPS,SEI。根据上述公式得出的type值与对应的类型如下:

value    type
32    VPS
33    SPS
34    PPS
39    SEI
19    IDR 帧
1    非IDR帧
根据值即可获取当前帧的类型,并进行相应的处理即可
(3)H265新增了VPS数据,视频参数集,主要用于传输视频分级信息,有利于兼容标准在可分级视频编码或多视点视频的扩展

(4)其中在有的视频文件中,SEI并不存在,是可选项

RTP封包的基本逻辑

(1)一个NALU打包成一个RTP包,只需要在一个12字节的RTP包头后添加去掉开始码的NALU即可
(这种模式在一个NALU的大小小于MTU时使用)。针对NALU类型是1,32,33,34,39的RTP包,只需要在拆包的时候,去掉RTP头,然后添加添加起始码即可
(2)一个NALU打包成几个RTP包(FUs模式),在12个字节的RTP头后面有两个字节的PayloadHdr和一个字节的FU
header。PayloadHdr的值等于NALU头的type位改为49(十进制)后的值,FU header第1位标记RTP包是否为NALU的第一片,第2位标记RTP包是否为NALU的最后一片。后6位是NALU头的type位。

(3)一个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解码将无法进行

附带RTP码流数据

VPS(完整一包)(pData[12] & 0x7E) >> 1 = (0x40 & 0x7E) >> 1 = 32 (0x20)
80 6c 00 03 00 00 1c 20 01 e0 a1 d7 40 01 0c 01 ff ff 01 60 00 00 03 00 b0 00 00 03 00 00 03 00 7b ac 0c 00 00 03 00 04 00 00 03 00 67 a0
SPS(完整一包)(pData[12] & 0x7E) >> 1 = (0x42 & 0x7E) >> 1 = 33 (0x21)
80 6c 00 04 00 00 1c 20 01 e0 a1 d7 42 01 01 01 60 00 00 03 00 b0 00 00 03 00 00 03 00 7b a0 03 c0 80 10 e5 8d ae 49 32 fc dc 04 04 04 
PPS(完整一包)(pData[12] & 0x7E) >> 1 = (0x44 & 0x7E) >> 1 = 34 (0x22)
80 6c 00 05 00 00 1c 20 01 e0 a1 d7 44 01 c0 f2 f0 3c 90
SEI(完整一包)(pData[12] & 0x7E) >> 1 = (0x4e & 0x7E) >> 1 = 39 (0x27)
80 6c 00 06 00 00 1c 20 01 e0 a1 d7 4e 01 e5 04 8e 1c 00 00 80
非IDR帧(完整一包)(pData[12] & 0x7E) >> 1 = (0x02 & 0x7E) >> 1 = 1(0x01)
80 ec 00 a7 00 00 2a 30 01 e0 a1 d7 02 01 d0 00 09 7f 48 16 b4 fd fb fb ae ae b5 b9 ef 58 94 c0 c8 be 8c 6f 59
IDR帧(开始第一包)62 01 93        93& 0x3F = 19
80 6c 00 07 00 00 1c 20 01 e0 a1 d7 62 01 93 af 13 68 4b e6 77 11 91 07 c3 8a 2b f4 75 3a d4 33 31 f0 f6 7b b6
IDR帧(中间一包)62 01 13 
80 6c 00 08 00 00 1c 20 01 e0 a1 d7 62 01 13 19 08 74 8a cf aa 9d 8c 02 f9 8d cb 08 1d 6f fc 99 09 24 db f7 ff e9 59 79 49 92 3e 3b b2 78 7d
IDR帧(最后一包)62 01 53
80 ec 00 a6 00 00 1c 20 01 e0 a1 d7 62 01 53 96 c7 1d 49 c3 d2 22 af 45 4f 28 57 8a 7a 2e 5a a4 f3 4f 42 25 df ac

解析说明:根据第一个字节的中间六位((pData[12] & 0x7E) >> 1 = 19) ,判断出是FU包,说明第三个字节中的最后一个字节的最后六位是帧的真正类型( pData[14] & 0x3F = 19 说明是IDR帧)

非IDR帧(开始第一包)
80 6c 01 31 00 02 16 60 01 e0 a1 d7 62 01 81 d0 00 59 7f 48 3a d0 fc 0a d2 36 92 f6 21 cd 40 9f
非IDR帧(最后一包)
80 ec 01 32 00 02 16 60 01 e0 a1 d7 62 01 41 9b 19 cb 51 d5 79 59 b0 18 64 00 92 d1 89

最终得到的码流数据如下:

00 00 00 01 40 01 0c 01 ff ff 01 60 00 00 03 00 b0 00 00 03 00 00 03 00 7b ac 0c 00 00 03 00 04 00 00 03 00 67 a0

00 00 00 01 42 01 01 01 60 00 00 03 00 b0 00 00 03 00 00 03 00 7b a0 03 c0 80 10 e5 8d ae 49 32 fc dc 04 04 04 

00 00 00 01 44 01 c0 f2 f0 3c 90

00 00 00 01 4e 01 e5 04 8e 1c 00 00 80

00 00 00 01 26 01  af 13 68 4b e6 77 11 91 07 c3 8a 2b f4 75 3a d4 33 31 f0 f6 7b b6 19 08 74 8a cf aa 9d 8c 02 f9 8d cb 08 1d 6f fc 99 09 24 db f7 ff e9 59 79 49 92 3e 3b b2 78 7d 96 c7 1d 49 c3 d2 22 af 45 4f 28 57 8a 7a 2e 5a a4 f3 4f 42 25 df ac

00 00 00 01 02 01 d0 00 59 7f 48 3a d0 fc 0a d2 36 92 f6 21 cd 40 9f 9b 19 cb 51 d5 79 59 b0 18 64 00 92 d1 89

    virtual bool ReadNextFrameByH265FromRTP(CVOS_MEDIA_TYPE_t media, CVOS_FRAME_INFO_t & info, std::string * pStrPacketBuf)
    {
        static std::uint8_t szStartCode[] = { 0x00, 0x00, 0x00, 0x01 };
        //收到的RTP包,保存在m_strRTPBuffer缓存中
        //RTP头12个字节,H265 NALU单元需要两个字节,对于FUs模式的RTP,还需要多一个字节保存FUs分包类型
        if (m_strRTPBuf.length() < 15)    return false;

        std::uint8_t* pData = (std::uint8_t*)m_strRTPBuf.c_str();
        std::uint8_t cRTPVersion = pData[0] & 0x80;
        //RTP协议的版本号,占2位,当前协议版本号为2
        if (0x80 != cRTPVersion)    return false;

        std::uint8_t cMark = pData[1] & 0x80;
        std::uint8_t cPayLoad = pData[1] & 0x7F;

        std::uint32_t nTimeStamp = (((pData[4] << 8) + pData[5]) << 16) + (pData[6] << 8) + pData[7];
        //获取FU-A分片的第一个字节的中间六位,判断分片类型
        std::uint8_t cFragmentationUnitType = (pData[12] & 0x7E) >> 1;

        if ((int)cFragmentationUnitType == 49)
        {
            std::uint8_t cFrameType = pData[14] & 0x3F;
            std::cout << "Type:"<< (int)cFragmentationUnitType <<"  " << (int)cFrameType << std::endl;
        }
        else
        {
            std::cout << (int)cFragmentationUnitType << std::endl;
        }
        bKeyFrame = CVOS_FRAME_TYPE_NON_KEY_FRAME;
        switch (cFragmentationUnitType)
        {
        case 1://非IDR帧,长度小于MTU单元
        {
            m_strCompleteOneFrame.append((char*)szStartCode, 4);
            m_strCompleteOneFrame.append(&m_strRTPBuf[12], m_strRTPBuf.size() - 12);
            m_bCompleteOneFrame = true;
            break;
        }
        case 32://视频参数集VPS
        case 33://序列参数集SPS
        case 34://序列参数集PPS
        case 39://补充增强信息SEI
        {
            //一般情况下都是单独完整的一个RTP包,直接去掉RTP头部
            m_strCompleteOneFrame.append((char*)szStartCode, 4);
            m_strCompleteOneFrame.append(&m_strRTPBuf[12], m_strRTPBuf.size() - 12);
            break;
        }
        case 49:
        {
            //获取FU Header分片的最后六位,判断当前的帧类型
            std::uint8_t cFrameType = pData[14] & 0x3F;
            if (19 == cFrameType)//IDR 帧
            {
                bKeyFrame = CVOS_FRAME_TYPE_KEY_FRAME;
            }
            else
            {
                //存在非IDR帧长度大于MTU单元,需要进行拆分的情况
                //std::cout << "not I frame" << std::endl;
            }
            std::uint8_t cStartBit = (pData[14] >> 7);
            std::uint8_t cEndBit = (pData[14] & 0x40) >> 6;
            if (1 == cStartBit)
            {
                m_strCompleteOneFrame.append((char*)szStartCode, 4);
                //根据FUs头第一个字节 和 FU Header 拼凑出NAL头部
                // ((pData[14] & 0x3F) << 1取FU Header的最后六位,并且移动到中间
                //pData[12] & 0x81)Fus第一个字节的中间六位置零,进行或操作
                unsigned char cNALHeader = (pData[12] & 0x81) | ((pData[14] & 0x3F) << 1);
                m_strCompleteOneFrame.append((char*)&cNALHeader, 1);
                m_strCompleteOneFrame.append((char*)&(m_strRTPBuf[13]), 1);
            }
            else if (1 == cEndBit)
            {
                //标志为完整的一帧
                m_bCompleteOneFrame = true;
            }
            m_strCompleteOneFrame.append(&m_strRTPBuf[15], m_strRTPBuf.size() - 15);
            break;
        }
        case 48:
        {
            /*
            一个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解码将无法进行
            */
            break;
        }
        default:
        {
            //目前不清楚是否还有其他的独立NALU类型单元,所以直接去掉RTP头
            //std::uint8_t cFragmentationUnitType = (pData[12] & 0x7E) >> 1;
            //std::cout << (int)cFragmentationUnitType << std::endl;
            m_strCompleteOneFrame.append((char*)szStartCode, 4);
            m_strCompleteOneFrame.append(&m_strRTPBuf[12], m_strRTPBuf.size() - 12);
            m_bCompleteOneFrame = true;
            break;
        }
        }
        bool bRet = m_bCompleteOneFrame;
        if (m_bCompleteOneFrame)
        {
            *pStrPacketBuf = m_strCompleteOneFrame;
            memset(&info, 0, sizeof(info));

            info.stream_id = 0;
            info.media_type = CVOS_MEDIA_TYPE_VIDEO;
            info.muxer = CVOS_MUXER_NONE;
            info.muxer_video_codec = CVOS_CODEC_TYPE_NONE;
            info.muxer_audio_codec = CVOS_CODEC_TYPE_NONE;
            info.video_width = 0;
            info.video_height = 0;
            info.codec = CVOS_CODEC_TYPE_H265;
            info.frame_type = bKeyFrame;

            m_strCompleteOneFrame.clear();
            m_bCompleteOneFrame = false;
        }
        return bRet;
    }