在之前兩篇關於EasyRTMPClient擴展支持HEVC(H.265)解決方案的文章中,我們已經完成了對H265的支持,本文主要闡述將H26和H265支持兼容起來,實現不同視頻編碼格式的自適應兼容適配。
1. 根據CodecId判斷數據編碼類型
根據視頻編碼ID判斷視頻編碼類型,如果視頻編碼ID==FlvCodeId_Hevc(12),則判斷視頻編碼格式爲H265,反之則爲H264(因爲目前我們只支持這兩種編碼格式的視頻推送),如下代碼所示:
parser_VideoTag *video_tag = (parser_VideoTag*)(buf+parser_offset);
FlvCodeId video_code_id = (FlvCodeId)(video_tag->code_id&0x0f);
if (video_code_id == FlvCodeId_Hevc)
{
av_frame.u32AVFrameFlag = EASY_SDK_VIDEO_CODEC_H265;// HEVC;
}
else
{
av_frame.u32AVFrameFlag = EASY_SDK_VIDEO_CODEC_H264;// 默認h264, 其他類型是否需要判斷?!;
}
2. 數據幀頭部判斷
根據FLV/RTMP擴展支持H265標準,支持HEVC的VideoTagHeader定義如下圖所示:
即 當CodecID == 12時,AVCPacketType爲HEVCPacketType:
-
如果HEVCPacketType爲0,表示HEVCVIDEOPACKET中存放的是HEVC sequence header;
-
如果HEVCPacketType爲1,表示HEVCVIDEOPACKET中存放的是HEVC NALU;
-
如果HEVCPacketType爲2,表示HEVCVIDEPACKET中存放的是HEVC end of sequence,即HEVCDecoderConfigurationRecord;
而當CodecID == 7時,AVCPacketType爲AVCPacketType:
-
如果AVCPacketType爲0,表示HEVCVIDEOPACKET中存放的是AVC sequence header;
-
如果AVCPacketType爲1,表示HEVCVIDEOPACKET中存放的是AVC NALU;
-
如果AVCPacketType爲2,表示HEVCVIDEPACKET中存放的是AVC end of sequence,即AVCDecoderConfigurationRecord;
EasyRTMPClient對sequence header的解析函數如下代碼段所示:
int ParserVideoSequencePacket(FlvCodeId video_code_id, char *buf,int len)
{
int parser_offset = 0;
char *parser_config = buf;
if (video_code_id == FlvCodeId_Hevc)
{
if(len <= sizeof(Parser_HEVCDecoderConfigurationRecord))
{
return -1001;
}
......’
//Parser HEVCDecoderConfigurationRecord
......
rtmpclient_h265_decode_sps((unsigned char *)sps_buf_, sps_len_, width_, height_);
}
else
{
if(len <= sizeof(parser_AVCDecoderHeader))
{
return -1001;
}
......’
//Parser HEVCDecoderHeader
......
rtmpclient_h264_decode_sps((unsigned char *)sps_buf_, sps_len_, width_, height_);
}
return 0;
}
3. 視頻數據體幀數據nalu類型判斷
根據FLV/RTMP擴展支持協議標準,支持H265的VideoTagBody定義如下, 擴展後的VideoTagBody如下圖所示(紅色字體爲HEVC新增內容)::
當CodecID爲12時,VideoTagBody中存放的就是HEVC視頻幀內容。
EasyRTMPClient視頻幀nalu解析如下代碼所示:
int ParserOneVideoNalu(EasyRTMPClient_AV_Frame& av_frame,char *buf,int len,char* processbuf)
{
if(processbuf == NULL || buf == NULL || len == 0)
{
return -3001;
}
if(sps_len_ == 0 || pps_len_ == 0)
{
printf("do not get sequence head yet\n");
return -3002;
}
int parse_offset = 0;
int nalu_len = 0;
int nalu_type = 0;
int processlen = 0;
while(parse_offset < len - 4)
{
nalu_len = ntohl(*(int*)(buf + parse_offset));
parse_offset += 4;
//如果視頻幀編碼類型爲H265
if(av_frame.u32AVFrameFlag == EASY_SDK_VIDEO_CODEC_H265)
{
nalu_type = (buf[parse_offset] >> 1) & 0x3F;
if(nalu_type == e_H265_NAL_UNIT_VPS)
{
ASSERT_PARSER(nalu_len,MAX_VPS_LEN);
memcpy(vps_buf_,buf + parse_offset,nalu_len);
vps_len_ = nalu_len;
parse_offset += nalu_len;
continue;
}
}else{
nalu_type = buf[parse_offset]&0x1F;
}
//H265 以及 H264的SPS頭解析兼容
if((av_frame.u32AVFrameFlag == EASY_SDK_VIDEO_CODEC_H265&&nalu_type ==e_H265_NAL_UNIT_SPS) ||
av_frame.u32AVFrameFlag == EASY_SDK_VIDEO_CODEC_H264&&nalu_type == e_H264_Frame_Type_Sps)
{
ASSERT_PARSER(nalu_len,MAX_PPS_LEN);
memcpy(sps_buf_,buf + parse_offset,nalu_len);
sps_len_ = nalu_len;
parse_offset += nalu_len;
if(width_ == 0 && sps_len_ > 0)
{
if(av_frame.u32AVFrameFlag == EASY_SDK_VIDEO_CODEC_H265)
rtmpclient_h265_decode_sps((unsigned char *)sps_buf_, sps_len_, width_, height_);
else
rtmpclient_h264_decode_sps((unsigned char *)sps_buf_, sps_len_, width_, height_);
}
continue;
}
//H265 以及 H264的PPS頭解析兼容
else if((av_frame.u32AVFrameFlag == EASY_SDK_VIDEO_CODEC_H265&&nalu_type ==e_H265_NAL_UNIT_PPS) ||
av_frame.u32AVFrameFlag == EASY_SDK_VIDEO_CODEC_H264&&nalu_type == e_H264_Frame_Type_Pps)
{
memcpy(pps_buf_,buf + parse_offset,nalu_len);
pps_len_ = nalu_len;
parse_offset += nalu_len;
continue;
}
//H265 以及 H264的I幀解析兼容
else if((av_frame.u32AVFrameFlag == EASY_SDK_VIDEO_CODEC_H265&&nalu_type >=e_H265_NAL_UNIT_SLICE_BLA&&nalu_type <=e_H265_NAL_UNIT_SLICE_CRA ) ||
av_frame.u32AVFrameFlag == EASY_SDK_VIDEO_CODEC_H264&&nalu_type == e_H264_Frame_Type_Idr)
{
if(av_frame.u32AVFrameFlag == EASY_SDK_VIDEO_CODEC_H265)
{
memcpy(processbuf + processlen,nalu_head_,4);
processlen += 4;
memcpy(processbuf + processlen,vps_buf_,vps_len_);
processlen += vps_len_;
}
.......
//拷貝SPS和PPS以及Idr nalu
......
av_frame.u32VFrameType = EASY_SDK_VIDEO_FRAME_I;
continue ;
}
//H265 以及 H264的P幀解析兼容
else if((av_frame.u32AVFrameFlag == EASY_SDK_VIDEO_CODEC_H265&&nalu_type >=e_H265_NAL_UNIT_SLICE_TRAIL_R&&nalu_type <=e_H265_NAL_UNIT_SLICE_TFD ) ||
av_frame.u32AVFrameFlag == EASY_SDK_VIDEO_CODEC_H264&&nalu_type == e_H264_Frame_Type_Slice)
{
memcpy(processbuf + processlen,nalu_head_,4);
processlen += 4;
memcpy(processbuf + processlen,buf + parse_offset,nalu_len);
processlen += nalu_len;
parse_offset += nalu_len;
av_frame.u32VFrameType = EASY_SDK_VIDEO_FRAME_P;
continue ;
}
else
{
parse_offset += nalu_len;
continue;
}
}
av_frame.pBuffer = (uint8_t *)processbuf;
av_frame.u32AVFrameLen = processlen;
return 0;
}
至此,EasyRTMPClient對H264和H265的兼容適配就完成了,我們可以通過EasyRTMPClient拉取任意編碼格式爲H264或者H265的RTMP進行拉流,均能取得完整的視頻幀數據進行解碼和播放。
歡迎大家下載EasyPlayer測試播放支持H265的RTMP流:
https://github.com/EasyDSS/EasyPlayer-RTMP-Win
https://github.com/EasyDSS/EasyPlayerPro-Win
有任何技術問題,歡迎大家和我進行技術交流:
[email protected]
大家也可以加入EasyPlayer流媒體播放器 QQ羣進行討論:
544917793