rtmp视频包结构
类型 | 长度 | 值 |
---|---|---|
FrameType | 4bit | 1表示关键帧,2表示非关键帧,3表示一次性帧,4为服务器保留,5表示视频信息或命令帧 |
CodecID | 4bit | 1表示JPG,2表示Sorenson H263,3表示屏幕录像,4表示VP6 ON2,,5表示带alphat通道的VP6 ON2,6表示版本2的屏幕录像,7表示avc,即h264 |
VideoData | 视频数据,根据CodecID的不同,此处字段内容不同,例如CodecID为2时,此处为H263包结构,当CodecID为7时,此处为AVC视频包结构 |
实际使用当中,经常用的是发送h264或者h265的包
对于h265, CodecID为7,对于h265,一般扩展的CodecID都是12
因此,对于h264视频包来说,第一个字节一般是0x17或0x27,
对于h265, 第一个字节一般是0x1c或0x2c
对于VideoData
字段,当CodecID为7时,结构是AVC视频包
AVC视频包结构
类型 | 长度 | 值 |
---|---|---|
AVCPacketType | 8bit | 0表示AVC Sequence头,1表示nalu, 2一般不支持较低level的h264 |
Composition time | 3 byte | 如果AVCPacketType为1表示Composition time offset, 其他值是全为0 |
Data | n byte | 如果AVCPacketType为0,此处是AVCDecoderConfigurationRecord,如果AVCPacketType,此处是视频数据,如果AVCPacketType为2, 此处为空 |
明显的,前2个字段比较明确,Composition time一般都填0
下面看下AVCDecoderConfigurationRecord
h264 AVCDecoderConfigurationRecord
参数名 | 长度 | 值 |
---|---|---|
configureVersion | 1 byte | 版本号,默认为1 |
AVCProfileIndication | 1 byte | profile定义,sps信息里面去除头部信息和sps标志后的第一个字节 |
profile_compatibility | 1 byte | profile支持级别,sps信息里面去除头部信息和sps标志后的第二个字节 |
AVCLevelIndication | 1 byte | level级别 sps头信息之后第3个字节 |
reserved | 6 bit | 保留,默认为’111111’ |
lengthSizeMinusOne | 2 bit | nalu unit长度 - 1,一般为3 |
reserved | 3 bit | 保留,默认为’111’ |
numOfSequenceParameterSets | 5 bit | sps的个数,一般为1,即后面只有1个sps,如果有多个则后面2个字段循环添加 |
sequenceParameterSetLength | 2 byte | sps长度,先填高8位,再填低8位 |
sequenceParameterSetNaluUnit | n byte | sps内容 |
numOfPictureParameterSets | 1 byte | pps个数,一般为1,如果有多个,后面的2个字段循环添加 |
pictureParameterSetLength | 2 byte | pps长度 |
pictureParameterSetNaLuUnit | n byte | pps内容 |
这样h264视频包结构就比较明晰了
再看看h265的HEVCDecoderConfigurationRecord
参数名 | 长度 | 值 |
---|---|---|
configureVersion | 1 byte | 版本号,默认为1 |
general_profile_space | 2 bit | 0, 其他值留作备用 |
general_tier_flag | 1 bit | 2个值,main tier和high tier,level 4和4以上支持High Tier |
general_profile_idc | 5 bit | 当general_profile_space等于0时,指示CVS符合的配置文件 |
附件A中规定 | ||
general_profile_compatibility_flags | 4 byte | 当general_profile_idc指定的profile不在 |
general_constraint_indicator_flags | 1 byte | |
general_level_idc | 1 byte | |
reserved1 | 4 bit | 默认’1111’ |
min_spatial_segmentation_idc_L | 4 bit | |
min_spatial_segmentation_idc_H | 1 byte | |
reserved1 | 6 bit | 默认’111111’ |
parallelismType | 2 bit | |
reserved1 | 6 bit | 默认’111111’ |
chromaFormat | 2 bit | |
reserved1 | 5 bit | 默认’11111’ |
bitDepthLumaMinus8 | 3 bit | |
reserved1 | 5 bit | 默认’11111’ |
bitDepthChromaMinus8 | 3 bit | |
avgFrameRate | 2 byte | |
constantFrameRate | 2 bit | |
numTemporalLayers | 3 bit | |
temporalIdNested | 1 bit | |
lengthSizeMinusOne | 2 bit | |
numOfArrays | 1 byte | vps,sps,pps个数,后续填充内容,与h264一样 |
librtmp 推流h265, 最开始参考的是:
https://blog.csdn.net/qq_33795447/article/details/89457581
里面的推流,用大牛那个播放器是可以播的,但是用金山修改的ffmpeg播不了
又分析了下,主要还是vps,sps,pps这些信息组包不对,
修改为下面的内容:
unsigned char body[1024] = { 0 };
int i = 0;
body[i++] = 0x1C;
body[i++] = 0x00;// AVC sequence header 1byte
body[i++] = 0x00;//composition time 3 byte
body[i++] = 0x00;
body[i++] = 0x00;
body[i++] = 0x01;
body[i++] = m_metaData.m_sps[6];
body[i++] = m_metaData.m_sps[7];
body[i++] = m_metaData.m_sps[8];
body[i++] = m_metaData.m_sps[9];
body[i++] = m_metaData.m_sps[12];
body[i++] = m_metaData.m_sps[13];
body[i++] = m_metaData.m_sps[14];
body[i++] = 0x00;
body[i++] = 0x00;
body[i++] = 0x00;
body[i++] = 0x00;
body[i++] = 0x00;
body[i++] = 0x00;
body[i++] = 0x00;
body[i++] = 0x00;
body[i++] = 0x00;
body[i++] = 0x00;
body[i++] = 0x00;
body[i++] = 0x00;
body[i++] = 0x00;
body[i++] = 0x03; //视频数据nal长度字节数-1,只取低2位
/* unsigned int(8) numOfArrays; 03 */
body[i++] = 0x03;
body[i++] = 0x20; //vps 32
body[i++] = 0x00;
body[i++] = 0x01;
body[i++] = (m_metaData.m_vpsLen >> 8) & 0xff;
body[i++] = (m_metaData.m_vpsLen) & 0xff;
memcpy(&body[i], m_metaData.m_vps, m_metaData.m_vpsLen);
i += m_metaData.m_vpsLen;
//sps
body[i++] = 0x21; //sps 33
body[i++] = 0x00;
body[i++] = 0x01;
body[i++] = (m_metaData.m_nSpsLen >> 8) & 0xff;
body[i++] = m_metaData.m_nSpsLen & 0xff;
memcpy(&body[i], m_metaData.m_sps, m_metaData.m_nSpsLen);
i += m_metaData.m_nSpsLen;
//pps
body[i++] = 0x22; //pps 34
body[i++] = 0x00;
body[i++] = 0x01;
body[i++] = (m_metaData.m_nPpsLen >> 8) & 0xff;
body[i++] = (m_metaData.m_nPpsLen) & 0xff;
memcpy(&body[i], m_metaData.m_pps, m_metaData.m_nPpsLen);
i += m_metaData.m_nPpsLen;
memcpy(m_RTMPPacket.m_body, (unsigned char*)body, i);
下面是推流抓包获取的十六进制数据样式
1c
000000000101600000009000000000005af000fcfdf8f800000f
03
20
0001
0018
40010c01ffff01600000030090000003000003005a959809
21
0001
002f
42010101600000030090000003000003005aa006b201e1df96566924cafff0280027f0100000030010000003019080
22
0001
0007
4401c172b46240
视频包推送修改为下面的内容:
if (isKeyFrame)
body[i++] = 0x1C;
else
body[i++] = 0x2C;
body[i++] = 0x01;// AVC NALU
body[i++] = 0x00;
body[i++] = 0x00;
body[i++] = 0x00;
// NALU size
body[i++] = (size>>24) & 0xFF;
body[i++] = (size>>16) & 0xFF;
body[i++] = (size>>8) & 0xFF;
body[i++] = size&0xff;
// NALU data
memcpy(&(body[i]),packet.data,size);
下面是视频帧包样式:
1c
01
000050
00001bf9
2601ae505c889c6440bc72fcdfaa1a66cfbdb1
如果希望推流一个,多个播放,则需要在每个i帧前发送vps,sps,pps的组包
测试可以用大牛播放器,或者金山修改的ffmpeg,或者EasyPlayerPro
参考链接:
https://blog.csdn.net/SwordTwelve/article/details/89522984
https://blog.csdn.net/qq_33795447/article/details/89457581
https://blog.csdn.net/dqxiaoxiao/article/details/94820599
https://blog.csdn.net/Dillon2015/article/details/104311186
https://blog.csdn.net/yue_huang/article/details/75126155