1.简介
众所周知,h264/h265的码流封装的时候有两种模式:ANNEXB和AVCC/HVCC。h264裸流、ps、ts等格式下是annexb;flv、MP4等格式下是avcc/hvcc。
如果完全按照标准进行封装,是一件比较麻烦的事情,本文介绍一种懒人办法,亲测有效。
介绍一下本人的开源流媒体,点个star,有兴趣一起开发的朋友也可以联系本人:SimpleMediaServer(流媒体): 旨在开发一个可商用的,功能丰富、高性能、高可扩展的流媒体服务。支持RTSP、RTMP、WEBRTC、GB28181、HTTP-FLV、HLS、JT1078等协议。各个协议可以单独剥离出来,相互独立;也可以互相转换协议。有兴趣一起开发开源项目的朋友可以联系本人(qq 1084855601),包括且不限于音视频后端、信令开发、控制台,前端等。https://gitee.com/inyeme/simple-media-server
2.AVCC
h264的annexb转为avcc还是比较简单的,按照标准走就行
*data++ = 0x01; //configurationversion
*data++ = spsBuffer[spsSize + 1]; //avcprofileindication
*data++ = spsBuffer[spsSize + 2]; //profilecompatibilty
*data++ = spsBuffer[spsSize + 3]; //avclevelindication
*data++ = 0xff; //reserved + lengthsizeminusone
*data++ = 0xe1; //num of sps
*data++ = (uint8_t)(spsLen >> 8); //sequence parameter set length high 8 bits
*data++ = (uint8_t)(spsLen); //sequence parameter set length low 8 bits
// data length 13
memcpy(data, spsBuffer.data() + spsSize, spsLen); //H264 sequence parameter set
data += spsLen; // 13 + spsLen
*data++ = 0x01; //num of pps
*data++ = (uint8_t)(ppsLen >> 8); //picture parameter set length high 8 bits
*data++ = (uint8_t)(ppsLen); //picture parameter set length low 8 bits
// 16 + spsLen + ppsLen
memcpy(data, ppsBuffer.data() + ppsSize, ppsLen); //H264 picture parameter set
avcc的第2、3、4个字节就是sps的第2、3、4个字节
3.HVCC
h265的annexb转hvcc比较复杂,想要完全按照标准去封装,需要对sps做比较全的解析,太麻烦了,在开发项目的时候,突发灵感,既然hvcc里的字段都是vps或sps里的字段,解码器应该不会真的去解析hvcc的字段,而是直接会去解析vps,sps,pps。那hvcc那些难搞的字段是不是可以随便填呢,抱着试一试的态度,写下代码:
string H265Track::getConfig()
{
if (!_vps || !_sps || !_pps) {
return "";
}
string config;
auto spsSize = _sps->startSize();
auto vpsSize = _vps->startSize();
auto ppsSize = _pps->startSize();
auto vpsBuffer = _vps->_buffer;
int vpsLen = _vps->size() - vpsSize;
auto spsBuffer = _sps->_buffer;
int spsLen = _sps->size() - spsSize;
auto ppsBuffer = _pps->_buffer;
int ppsLen = _pps->size() - ppsSize;
config.resize(38 + vpsLen + spsLen + ppsLen);
auto data = (char*)config.data();
// *data++ = 0x1c; //key frame, AVC
// *data++ = 0x00; //avc sequence header
// *data++ = 0x00; //composit time
// *data++ = 0x00; //composit time
// *data++ = 0x00; //composit time
*data++ = 0x01; //configurationversion
// 6 byte
*data++ = spsBuffer[spsSize + 5]; //general_profile_space(2), general_tier_flag(1), general_profile_idc(5)
*data++ = spsBuffer[spsSize + 6]; //general_profile_compatibility_flags
*data++ = spsBuffer[spsSize + 7];
*data++ = spsBuffer[spsSize + 8];
*data++ = spsBuffer[spsSize + 9];
*data++ = spsBuffer[spsSize + 10]; //general_constraint_indicator_flags
*data++ = spsBuffer[spsSize + 11];
*data++ = spsBuffer[spsSize + 12];
*data++ = spsBuffer[spsSize + 13];
*data++ = spsBuffer[spsSize + 14];
*data++ = spsBuffer[spsSize + 15];
*data++ = spsBuffer[spsSize + 16]; // general_level_idc
*data++ = spsBuffer[spsSize + 17]; // reserved(4),min_spatial_segmentation_idc(12)
*data++ = spsBuffer[spsSize + 18];
*data++ = spsBuffer[spsSize + 19]; // reserved(6),parallelismType(2)
*data++ = spsBuffer[spsSize + 20]; // reserved(6),chromaFormat(2)
// 22 byte
// 下面几个0xff乱填的,貌似不影响播放
*data++ = 0xf << 4 | 0xf; reserved(5),bitDepthLumaMinus8(3)
*data++ = 0xff; // reserved(5),bitDepthChromaMinus8(3)
*data++ = 0xff; //avgFrameRate
*data++ = 0xff;
*data++ = 0xff; //constantFrameRate(2),numTemporalLayers(3),temporalIdNested(1),lengthSizeMinusOne(2)
// *data++ = 0xff;
// *data++ = 0xff;
// *data++ = 0xff;
// *data++ = 0xff;
// 默认vps,sps,pps 各一个
*data++ = 3; //numOfArrays
//28 byte
*data++ = 1 << 7 | H265NalType::H265_VPS & 0x3f; //array_completeness(1),reserved(1),NAL_unit_type(6)
// 一个vps
*data++ = 0; //numNalus
*data++ = 1;
*data++ = (uint8_t)(vpsLen >> 8); //sequence parameter set length high 8 bits
*data++ = (uint8_t)(vpsLen); //sequence parameter set length low 8 bits
memcpy(data, vpsBuffer.data() + vpsSize, vpsLen); //H264 sequence parameter set
data += vpsLen;
// 37 + vpsLen
*data++ = 1 << 7 | H265NalType::H265_SPS & 0x3f;
// 一个sps
*data++ = 0;
*data++ = 1;
*data++ = (uint8_t)(spsLen >> 8); //sequence parameter set length high 8 bits
*data++ = (uint8_t)(spsLen); //sequence parameter set length low 8 bits
memcpy(data, spsBuffer.data() + spsSize, spsLen); //H264 sequence parameter set
data += spsLen;
// 38 + vpsLen + spsLen
*data++ = 1 << 7 | H265NalType::H265_PPS & 0x3f;
// 一个pps
*data++ = 0;
*data++ = 1;
*data++ = (uint8_t)(ppsLen >> 8); //sequence parameter set length high 8 bits
*data++ = (uint8_t)(ppsLen); //sequence parameter set length low 8 bits
memcpy(data, ppsBuffer.data() + ppsSize, ppsLen); //H264 sequence parameter set
data += ppsLen;
// 43 + vpsLen + spsLen + ppsLen
return config;
}
经实测,rtmp协议可以正常播放。
结束语
本文介绍的方法,在开发过程中可以省下点时间,但是想学习hvcc和sps结构的,还是仔细地按照标准进行封装。