在音视频领域,融合推流,低码流,低延迟,浏览器H5化是一个降低成本,提升用户体验的重要手段。同时适配现有直播的生态也是一个必要条件。
在满足上述要求的情况下,我做了以下实践,取得了良好的效果。
在实践中,我们选择采用了成熟的rtmp做为推流的主要手段,srs直播多媒体服务器的顶级生态位不可或缺,H265作为良好的视频编解码协议,在节约40%以上的码流带宽情况下比H264拥有更清晰的画质和更好的运动画面表现力,新兴的浏览器传输硬解渲染技术作为跨平台web化的门户比起桌面播放器有太多优势。
在设备端推流选取了性价比很高的瑞星微rk3568作为硬件载体,其拥有60fps 1080p H265编码能力,内置opengl可以实现低延迟零拷贝的多窗口实时渲染能力。在实践中,我们做了大量的优化工作,提升了多路网络摄像头和本地摄像头及桌面的采集编码和转发推流能力。
srs作为国内开源多媒体服务器的翘楚,拥有大量的用户,积极拥抱其生态是必然选择,在对接srs rtmp推流时,因为其有严格的格式验证,需要格外小心,不然会导致解析码流失败,当然这个跟良好兼容性形成了鲜明的对比,当然这个是见仁见智的。我们在对接过程中就遇到了一些坑,在这里也提醒其他玩家特别注意。
下面就是我们处理码流进行推流的核心代码,贴出来以供参考
void VideoPushChannel::onencodeData(AVPacket *packet,int encodertype) {
uint8_t *ptr =packet->data;
size_t len = packet->size;
uint64_t dts=packet->dts;
uint64_t pts=packet->pts;
uint16_t frametype=0;
bool isH265=false;
if(ptr==NULL||len<=0) return;
unsigned char * psrc=ptr;
unsigned int srclen=len;
unsigned int packelen=0;
unsigned char nalutype=0;
unsigned int scrindex=0;
unsigned char destCount=0;
bool bend=false;
do {
// GetNaluSlice2(unsigned char * scr,uint32_t scrlen,uint32_t *packetlen,uint8_t * nalutype ,uint16_t * frametype,bool * endframe,uint32_t *pscroffset,uint8_t *destCount,bool* isH265);
int result = Nalu::GetNaluSlice2(psrc, srclen, &packelen, &nalutype, &frametype, &bend, &scrindex, &isH265,&destCount);
if (result == -1) {
break;
} else {
if (isH265) {
processH265Nalu(psrc, scrindex, packelen, nalutype,pts,dts);
} else {
processH264Nalu(psrc, scrindex, packelen, nalutype,pts,dts);
}
}
// 更新psrc和srclen以处理下一个NALU
psrc += scrindex + packelen;
srclen -= scrindex + packelen;
} while (!bend && srclen > 0);
}
在rtmp推流组包时,网上基本上就是建议以下的方式
body[i++] = 0x01; //configurationVersion 必须为1
//general_profile_idc 8bit
body[i++] = sps[1]; //HEVCProfileIndication
//general_profile_compatibility_flags 32 bit
body[i++] = sps[2];//profile_compatibility
body[i++] = sps[3];//HEVCLevelIndication
body[i++] = 0x03;//sps[4];//lengthSizeMinusOne 0x03
body[i++] = 0xE1;//sps[5];//sps number SPS的序号,是0xE1
// 48 bit NUll nothing deal in rtmp general_constraint_indicator_flags: 48bits
body[i++] = sps[6];
body[i++] = sps[7];
body[i++] = sps[8];
body[i++] = sps[9];
body[i++] = sps[10];
body[i++] = sps[11];
//general_level_idc //general_level_idc: 8bits
body[i++] = sps[12];
// 48 bit NUll nothing deal in rtmp
//min_spatial_segmentation_idc: xxxx 14bits
//parallelism_type: xxxx xx 2bits
//chroma_format: xxxx xx 2bits
//bit_depth_luma_minus8: xxxx x 3bits
//bit_depth_chroma_minus8: xxxx x 3bits
body[i++] = 0x00;
body[i++] = 0x00;
body[i++] = 0x00;
body[i++] = 0x00;
body[i++] = 0x00;
body[i++] = 0x00;
//bit(16) avgFrameRate;
body[i++] = 0x00;
body[i++] = 0x00;
// bit(2) constantFrameRate;
// bit(3) numTemporalLayers;
// bit(1) temporalIdNested;
body[i++] = 0x00;
// unsigned int(8) numOfArrays; 03
body[i++] = 0x03;
但是用这个组包方式肯定是调不通的,需要解析vps sps pps得到HEVCDecoderConfigurationRecord,然后填充进去,因为srs做了严格的HEVCDecoderConfigurationRecord解析,所以大家还是不要偷懒
ff_buff_write_hvcc(&body[i],pps,pps_len,sps,sps_len,vps,vps_len);
i+=22;
// unsigned int(8) numOfArrays; 03
body[i++] = 0x03;
按这个对接后基本上就可以愉快的用srs自带的播放器进行H265预览了,需要注意的是srs启动时采用./objs/srs -c ./conf/hevc.flv.conf 这个配置,不得不说,srs作为开源的项目能够做的这么完备还是非常牛的,在这里给他一个大大的赞!
当然做完这些,我们并不满足,因为预览的延迟,并没有达到毫秒级,于是我们做了无插件H265网页低延迟播放器,从srs拉取rtmp流,而不是flv流,实现了webrtc级的低延迟预览,下面是对比测试效果。
当然如果直接采用我们自己的架构延迟会更低
音视频是很复杂 的一门综合性应用,值得深入挖掘,特别是结合实际的落地还有很长的路要走,希望能在漫漫长路的旅途中与志同道合的您结识并相伴而行