在上一篇 SkeyeRTMPClient拉取RTMP流扩大反对HEVC(H.265)解决方案 中对于HEVCDecoderConfigurationRecord构造解析的解说存在一些表述上不分明的中央,本文为之续篇,重点对HEVC格局的MetaData构造的解析进行解说。
在SkeyeRTMPPusher扩大反对H265的解决方案讲述时。咱们对Metadata构造进行过详解,大家能够回顾一下这篇文章RTMP推送扩大反对HEVC(H265)之Metadata构造填写详解,重点来了,因为失常状况下,咱们只须要从MetaData中取出对咱们解码有用的数据头(即VPS,SPS和PPS),所以咱们对HEVCDecoderConfigurationRecord填充的MetaData其余数据并不关怀,然而,在解析时,咱们须要对该构造所有数据都解析进去,以保障能准确无误的获取到咱们所须要的数据头信息。
再次回顾HEVCDecoderConfigurationRecord构造:
typedef struct HEVCDecoderConfigurationRecord { uint8_t configurationVersion; uint8_t general_profile_space; uint8_t general_tier_flag; uint8_t general_profile_idc; uint32_t general_profile_compatibility_flags; uint64_t general_constraint_indicator_flags; uint8_t general_level_idc; uint16_t min_spatial_segmentation_idc; uint8_t parallelismType; uint8_t chromaFormat; uint8_t bitDepthLumaMinus8; uint8_t bitDepthChromaMinus8; uint16_t avgFrameRate; uint8_t constantFrameRate; uint8_t numTemporalLayers; uint8_t temporalIdNested; uint8_t lengthSizeMinusOne; uint8_t numOfArrays; HVCCNALUnitArray *array;} HEVCDecoderConfigurationRecord;
而事实上,该构造如果间接填入到MetaData中是不正确的,咱们看ffmpeg中hevc.c文件中的实现,该构造具体申明如下:
// The CodecPrivate syntax shall follow the// syntax of HEVCDecoderConfigurationRecord// defined in ISO/IEC 14496-15.//// The number zero (0) shall be written to// the configurationVersion variable until// official finalization of 14496-15, 3rd ed.//// After its finalization, this field and the// following CodecPrivate structure shall// follow the definition of the// HEVCDecoderConfigurationRecord in 14496-15.unsigned int(8) configurationVersion;unsigned int(2) general_profile_space;unsigned int(1) general_tier_flag;unsigned int(5) general_profile_idc;unsigned int(32) general_profile_compatibility_flags;unsigned int(48) general_constraint_indicator_flags;unsigned int(8) general_level_idc;bit(4) reserved = ‘1111’b;unsigned int(12) min_spatial_segmentation_idc;bit(6) reserved = ‘111111’b;unsigned int(2) parallelismType;bit(6) reserved = ‘111111’b;unsigned int(2) chromaFormat;bit(5) reserved = ‘11111’b;unsigned int(3) bitDepthLumaMinus8;bit(5) reserved = ‘11111’b;unsigned int(3) bitDepthChromaMinus8;bit(16) avgFrameRate;bit(2) constantFrameRate;bit(3) numTemporalLayers;bit(1) temporalIdNested;unsigned int(2) lengthSizeMinusOne;unsigned int(8) numOfArrays;for (j=0; j < numOfArrays; j++) { bit(1) array_completeness; unsigned int(1) reserved = 0; unsigned int(6) NAL_unit_type; unsigned int(16) numNalus; for (i=0; i< numNalus; i++) { unsigned int(16) nalUnitLength; bit(8*nalUnitLength) nalUnit; }}
从上代码段咱们能够看出,以general_constraint_indicator_flags这个参数为例,构造体申明位宽64,而理论位宽是48,,所以构造体申明的参数位宽和理论位宽可能是不对等的,这就将导致解析MetaData时产生错位,从而解析产生谬误,从而,咱们从新意识HEVCDecoderConfigurationRecord,并申明其构造如下:
// RTMP扩大反对HEVC(H.265) [4/18/2019 SwordTwelve]typedef struct _Parser_HEVCDecoderConfigurationRecord { uint8_t configurationVersion; uint8_t general_profile_space:2; uint8_t general_tier_flag:1; uint8_t general_profile_idc:5; uint32_t general_profile_compatibility_flags;//6 uint8_t general_constraint_indicator_flags[6];//12 uint8_t general_level_idc; uint8_t reserved1:4;// bit(4) reserved = ‘1111’b; uint8_t min_spatial_segmentation_idc_L:4;//12位之低4位 uint8_t min_spatial_segmentation_idc_H;//12位之高8位 uint8_t reserved2:6;//bit(6) reserved = ‘111111’b; uint8_t parallelismType:2; uint8_t reserved3:6;//bit(6) reserved = ‘111111’b; uint8_t chromaFormat:2; uint8_t reserved4:5;//bit(5) reserved = ‘11111’b; uint8_t bitDepthLumaMinus8:3; uint8_t reserved5:5;//bit(5) reserved = ‘11111’b; uint8_t bitDepthChromaMinus8:3; uint16_t avgFrameRate; uint8_t constantFrameRate:2; uint8_t numTemporalLayers:3; uint8_t temporalIdNested:1; uint8_t lengthSizeMinusOne:2; uint8_t numOfArrays; //Parser_HVCCNALUnitArray *array;} Parser_HEVCDecoderConfigurationRecord;
当初位宽曾经对齐,咱们能够间接从MetaData外面讲该构造拷贝进去,从而获取到正确的参数和值,如下代码所示:
Parser_HEVCDecoderConfigurationRecord *decoder_header = (Parser_HEVCDecoderConfigurationRecord*)((char*)parser_config); parser_offset += sizeof(Parser_HEVCDecoderConfigurationRecord); int nNumOfArrays = decoder_header->numOfArrays; for (int i=0; i<nNumOfArrays; i++) { ...... }
同理,咱们从MetaData中拷贝出nal单元数据头也是须要思考这个问题,这里咱们申明Parser_HVCCNALUnitArray构造如下:
// RTMP扩大反对HEVC(H.265) [4/18/2019 SwordTwlve]typedef struct _Parser_HVCCNALUnitArray { uint8_t NAL_unit_type;// uint16_t numNalus;// uint16_t *nalUnitLength;// uint8_t **nalUnit;} Parser_HVCCNALUnitArray;
如上代码所示,为了保障字节对齐,咱们只保留NAL_unit_type参数,通过字节拷贝运行,计算出nalu的数量numNalus,而后再从nalunit数组中获得咱们须要的头信息。
Parser_HVCCNALUnitArray* pNALUnit = (Parser_HVCCNALUnitArray*)((char *)parser_config+parser_offset); parser_offset += sizeof(Parser_HVCCNALUnitArray); int numNalus = ntohs(*(unsigned short *)((char *)parser_config + parser_offset)); parser_offset += 2; for (int nI=0; nI<numNalus; nI++) { ...... }