EasyRTMPClient擴展支持HEVC(H.265)解決方案之HEVCDecoderConfigurationRecord結構詳解

在上一篇 EasyRTMPClient拉取RTMP流擴展支持HEVC(H.265)解決方案 中關於HEVCDecoderConfigurationRecord結構解析的講解存在一些表述上不清楚的地方,本文爲之續篇,重點對HEVC格式的MetaData結構的解析進行講解。
在EasyRTMP擴展支持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++)
	{
		......
	}

歡迎大家下載EasyPlayer測試播放支持H265的RTMP流:
https://github.com/EasyDSS/EasyPlayer-RTMP-Win
https://github.com/EasyDSS/EasyPlayerPro-Win

有任何技術問題,歡迎大家和我進行技術交流:
[email protected]

大家也可以加入EasyPlayer流媒體播放器 QQ羣進行討論:
544917793

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章