在之前两篇对于 SkeyeRTMPClient 扩大反对 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 = SKEYE_SDK_VIDEO_CODEC_H265;// HEVC;}
else
{av_frame.u32AVFrameFlag = SKEYE_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;
SkeyeRTMPClient 对 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 视频帧内容。
SkeyeRTMPClient 视频帧 nalu 解析如下代码所示:
int ParserOneVideoNalu(SkeyeRTMPClient_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 == SKEYE_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 == SKEYE_SDK_VIDEO_CODEC_H265&&nalu_type ==e_H265_NAL_UNIT_SPS) ||
av_frame.u32AVFrameFlag == SKEYE_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 == SKEYE_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 == SKEYE_SDK_VIDEO_CODEC_H265&&nalu_type ==e_H265_NAL_UNIT_PPS) ||
av_frame.u32AVFrameFlag == SKEYE_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 == SKEYE_SDK_VIDEO_CODEC_H265&&nalu_type >=e_H265_NAL_UNIT_SLICE_BLA&&nalu_type <=e_H265_NAL_UNIT_SLICE_CRA) ||
av_frame.u32AVFrameFlag == SKEYE_SDK_VIDEO_CODEC_H264&&nalu_type == e_H264_Frame_Type_Idr)
{if(av_frame.u32AVFrameFlag == SKEYE_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 = SKEYE_SDK_VIDEO_FRAME_I;
continue ;
}
//H265 以及 H264 的 P 帧解析兼容
else if((av_frame.u32AVFrameFlag == SKEYE_SDK_VIDEO_CODEC_H265&&nalu_type >=e_H265_NAL_UNIT_SLICE_TRAIL_R&&nalu_type <=e_H265_NAL_UNIT_SLICE_TFD) ||
av_frame.u32AVFrameFlag == SKEYE_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 = SKEYE_SDK_VIDEO_FRAME_P;
continue ;
}
else
{
parse_offset += nalu_len;
continue;
}
}
av_frame.pBuffer = (uint8_t *)processbuf;
av_frame.u32AVFrameLen = processlen;
return 0;
}
至此,SkeyeRTMPClient 对 H264 和 H265 的兼容适配就实现了,咱们能够通过 SkeyeRTMPClient 拉取任意编码格局为 H264 或者 H265 的 RTMP 进行拉流,均能获得残缺的视频帧数据进行解码和播放。