准备知识
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;
}