共计 4463 个字符,预计需要花费 12 分钟才能阅读完成。
在上一篇 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++)
{......}