近期SkeyePlayer(windows)更新已全面支持H265的RTSP流的解码播放,这里就支持H265过程做简要介绍;

一、 libSkeyeRTSPClient库已支持H265视频源的RTSP流的拉取和解析

二、H265头解析
H265和H264类似,不过其NAL type格式更多样化,除了SPS,PPS之外,还增加了VPS,下面就针对H265帧nal 头做简单分析;
首先,看X265源码中的H265nal头格式定义:

//H265 NAL type
//this enum have been defined in x265.h
typedef enum tagH265NalUnitType
{NAL_UNIT_CODED_SLICE_TRAIL_N = 0,   // 0  NAL_UNIT_CODED_SLICE_TRAIL_R,   // 1  NAL_UNIT_CODED_SLICE_TSA_N,     // 2  NAL_UNIT_CODED_SLICE_TLA,       // 3   // Current name in the spec: TSA_R  NAL_UNIT_CODED_SLICE_STSA_N,    // 4  NAL_UNIT_CODED_SLICE_STSA_R,    // 5  NAL_UNIT_CODED_SLICE_RADL_N,    // 6  NAL_UNIT_CODED_SLICE_DLP,       // 7 // Current name in the spec: RADL_R  NAL_UNIT_CODED_SLICE_RASL_N,    // 8  NAL_UNIT_CODED_SLICE_TFD,       // 9 // Current name in the spec: RASL_R  NAL_UNIT_RESERVED_10,NAL_UNIT_RESERVED_11,NAL_UNIT_RESERVED_12,NAL_UNIT_RESERVED_13,NAL_UNIT_RESERVED_14,NAL_UNIT_RESERVED_15,           NAL_UNIT_CODED_SLICE_BLA,       // 16   // Current name in the spec: BLA_W_LP  NAL_UNIT_CODED_SLICE_BLANT,     // 17   // Current name in the spec: BLA_W_DLP  NAL_UNIT_CODED_SLICE_BLA_N_LP,  // 18  NAL_UNIT_CODED_SLICE_IDR,       // 19  // Current name in the spec: IDR_W_DLP  NAL_UNIT_CODED_SLICE_IDR_N_LP,  // 20  NAL_UNIT_CODED_SLICE_CRA,       // 21  NAL_UNIT_RESERVED_22,NAL_UNIT_RESERVED_23,NAL_UNIT_RESERVED_24,NAL_UNIT_RESERVED_25,NAL_UNIT_RESERVED_26,NAL_UNIT_RESERVED_27,NAL_UNIT_RESERVED_28,NAL_UNIT_RESERVED_29,NAL_UNIT_RESERVED_30,NAL_UNIT_RESERVED_31,NAL_UNIT_VPS,                   // 32  NAL_UNIT_SPS,                   // 33  NAL_UNIT_PPS,                   // 34  NAL_UNIT_ACCESS_UNIT_DELIMITER, // 35  NAL_UNIT_EOS,                   // 36  NAL_UNIT_EOB,                   // 37  NAL_UNIT_FILLER_DATA,           // 38  NAL_UNIT_SEI,                   // 39 Prefix SEI  NAL_UNIT_SEI_SUFFIX,            // 40 Suffix SEI  NAL_UNIT_RESERVED_41,NAL_UNIT_RESERVED_42,NAL_UNIT_RESERVED_43,NAL_UNIT_RESERVED_44,NAL_UNIT_RESERVED_45,NAL_UNIT_RESERVED_46,NAL_UNIT_RESERVED_47,NAL_UNIT_UNSPECIFIED_48,NAL_UNIT_UNSPECIFIED_49,NAL_UNIT_UNSPECIFIED_50,NAL_UNIT_UNSPECIFIED_51,NAL_UNIT_UNSPECIFIED_52,NAL_UNIT_UNSPECIFIED_53,NAL_UNIT_UNSPECIFIED_54,NAL_UNIT_UNSPECIFIED_55,NAL_UNIT_UNSPECIFIED_56,NAL_UNIT_UNSPECIFIED_57,NAL_UNIT_UNSPECIFIED_58,NAL_UNIT_UNSPECIFIED_59,NAL_UNIT_UNSPECIFIED_60,NAL_UNIT_UNSPECIFIED_61,NAL_UNIT_UNSPECIFIED_62,NAL_UNIT_UNSPECIFIED_63,NAL_UNIT_INVALID,
}H265NalUnitType;
#endif

我们可以看到其中VPS, SPS和PPS的定义:
NAL_UNIT_VPS, // 32
NAL_UNIT_SPS, // 33
NAL_UNIT_PPS, // 34
同样,我们也很容易知道P帧NAL type定义是0-9, I帧定义是16-21;可见H265的NAL type定义比H264要多样化,判断也不限制于一种类型;
同时,测试发现,实际H265帧数据中的VPS=0x40 , SPS=0x42, PPS=0x44, 通过换算,我们不难得出:
NALtype*2 = 实际的流中的NaLType;
具体解析过程如下:

 //输入的pbuf必须包含start code(00 00 00 01)
int GetH265VPSandSPSandPPS(char *pbuf, int bufsize, char *_vps, int *_vpslen, char *_sps, int *_spslen, char *_pps, int *_ppslen)
{char vps[512]={0}, sps[512] = {0}, pps[128] = {0};int vpslen=0, spslen=0, ppslen=0, i=0, iStartPos=0, ret=-1;int iFoundVPS=0, iFoundSPS=0, iFoundPPS=0, iFoundSEI=0;if (NULL == pbuf || bufsize<4)   return -1;for (i=0; i<bufsize; i++){if ( (unsigned char)pbuf[i] == 0x00 && (unsigned char)pbuf[i+1] == 0x00 && (unsigned char)pbuf[i+2] == 0x00 && (unsigned char)pbuf[i+3] == 0x01 ){printf("0x%X\n", (unsigned char)pbuf[i+4]);switch ((unsigned char)pbuf[i+4]){case 0x40:      //VPS{iFoundVPS = 1;iStartPos = i+4;}break;case 0x42:        //SPS{if (iFoundVPS == 0x01 && i>4){vpslen = i-iStartPos;if (vpslen>256)   return -1;          //vps长度超出范围memset(vps, 0x00, sizeof(vps));memcpy(vps, pbuf+iStartPos, vpslen);}iStartPos = i+4;iFoundSPS = 1;}break;case 0x44:      //PPS{if (iFoundSPS == 0x01 && i>4){spslen = i-iStartPos;if (spslen>256)   return -1;memset(sps, 0x0, sizeof(sps));memcpy(sps, pbuf+iStartPos,  spslen);}iStartPos = i+4;iFoundPPS = 1;}break;case 0x4E:       //Prefix SEI  case 0x50:        //Suffix SEI case 0x20:     //I frame 16case 0x22:      //I frame 17case 0x24:      //I frame 18case 0x26:      //I frame 19case 0x28:      //I frame 20case 0x2A:      //I frame 21(acturally we should find naltype 16-21){if (iFoundPPS == 0x01 && i>4){ppslen = i-iStartPos;if (ppslen>256)    return -1;memset(pps, 0x0, sizeof(pps));memcpy(pps, pbuf+iStartPos,  ppslen);}iStartPos = i+4;iFoundSEI = 1;}break;default:break;}}if (iFoundSEI == 0x01)     break;}if (iFoundVPS == 0x01){if (vpslen < 1){if (bufsize < sizeof(vps)){vpslen = bufsize-4;memset(vps, 0x00, sizeof(vps));memcpy(vps, pbuf+4, vpslen);}}if (vpslen > 0){if (NULL != _vps)   memcpy(_vps, vps, vpslen);if (NULL != _vpslen)    *_vpslen = vpslen;}ret = 0;}if (iFoundSPS == 0x01){if (spslen < 1){if (bufsize < sizeof(sps)){spslen = bufsize-4;memset(sps, 0x00, sizeof(sps));memcpy(sps, pbuf+4, spslen);}}if (spslen > 0){if (NULL != _sps)   memcpy(_sps, sps, spslen);if (NULL != _spslen)    *_spslen = spslen;}ret = 0;}if (iFoundPPS == 0x01){if (ppslen < 1){if (bufsize < sizeof(pps)){ppslen = bufsize-4;memset(pps, 0x00, sizeof(pps));memcpy(pps, pbuf+4, ppslen); //pps}}if (ppslen > 0){if (NULL != _pps)   memcpy(_pps, pps, ppslen);if (NULL != _ppslen)    *_ppslen = ppslen;}ret = 0;}return ret;
}

三、 解码器需支持H265
解码器直接使用最新的FFMPEG库即支持H265解码,且软解效率还可以,大家如果不知道怎么用,可以去看看ffplay的源码,这里不做过多赘述;这里就SkeyePlayer调用遇到的问题做简单说明:
1> 旧版的ffmpeg以及live555等对H265的定义是对“H265”子串做的字串格式组合,而新版的FFMPEG使用的自定义的顺序定义的枚举类型,所以在使用过程中可能出现对应不上的情况,比如,在libSkeyeRTSPClient库中对H265的定义为:#define Skeye_SDK_VIDEO_CODEC_H265 0x48323635 /* 1211250229 */
而FFMPEG中定义H265(HEVC)格式为174,SkeyePlayer中进行格式统一代码如下:

 //H265 codecID改成FFMPEG新版的int nCodec = (_frameinfo->codec == Skeye_SDK_VIDEO_CODEC_H265) ? 174 : _frameinfo->codec;

2> SkeyePlayer中之前对关键帧帧解码失败的处理是将以该I帧为关键帧为依托的所有P帧丢弃,当然这从某种程度上是可以避免花屏的,但是测试解码H265时发现,H265的第一个I帧会经常解码失败,经调试发现其实是FFNPEG的解码函数返回没有解码完成的结果被程序判断为解码失败,而这个时候应该不做任何处理等下一次返回的时候就能获取到正确的返回结果了,SkeyePlayer处理如下:

         nRet = FFD_DecodeVideo3(pDecoderObj->ffDecoder, pbuf, frameinfo.length, pThread->yuvFrame[pThread->decodeYuvIdx].pYuvBuf, frameinfo.width, frameinfo.height, lTimestamp, lTimestamp);if (0 != nRet){if(nRet == -4)//-4表示为当前帧尚未解码完成,不作为错误判断{_TRACE("视频帧解码尚未完成[%d]... framesize:%d   %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X\n", nRet, frameinfo.length, (unsigned char)pbuf[0], (unsigned char)pbuf[1], (unsigned char)pbuf[2], (unsigned char)pbuf[3], (unsigned char)pbuf[4],(unsigned char)pbuf[5], (unsigned char)pbuf[6], (unsigned char)pbuf[7], (unsigned char)pbuf[8], (unsigned char)pbuf[9]);}else{_TRACE("视频帧解解码失败[%d]... framesize:%d   %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X\n", nRet, frameinfo.length, (unsigned char)pbuf[0], (unsigned char)pbuf[1], (unsigned char)pbuf[2], (unsigned char)pbuf[3], (unsigned char)pbuf[4],(unsigned char)pbuf[5], (unsigned char)pbuf[6], (unsigned char)pbuf[7], (unsigned char)pbuf[8], (unsigned char)pbuf[9]);if (frameinfo.type == Skeye_SDK_VIDEO_FRAME_I)      //关键帧{_TRACE("[ch%d]当前关键帧解码失败...\n", pThread->channelId);#ifdef _DEBUGFILE *f = fopen("keyframe.txt", "wb");if (NULL != f){fwrite(pbuf, 1, frameinfo.length, f);fclose(f);}#endif}else{#ifdef _DEBUGFILE *f = fopen("pframe.txt", "wb");if (NULL != f){fwrite(pbuf, 1, frameinfo.length, f);fclose(f);}#endif}pThread->findKeyframe = 0x01;}
经测试,经过如上代码 修改就能有效的避免的解码H265视频的时候开头帧总是会局部卡帧或者花屏的情况出现。

四、H265格式视频写MP4

这里接着之前SkeyePlayer系列的写MP4篇讲,将H265封装MP4;

1> 解析H265的头,或者VPS,SPS和PPS
从H265帧中取出NAL头在上文已经作过讲解这里就不做过多赘述;

2> 写入VPS, SPS和PPS 等关键解码信息

bool SkeyeMP4Writer::WriteH265VPSSPSandPPS(unsigned char*vps, int vpslen, unsigned char*sps, int spslen,unsigned char*pps, int ppslen, int width, int height)
{if (m_nCreateFileFlag&ZOUTFILE_FLAG_VIDEO){m_videtrackid = gf_isom_new_track(p_file, 0, GF_ISOM_MEDIA_VISUAL, 1000);gf_isom_set_track_enabled(p_file, m_videtrackid, 1);}else{return false;}p_videosample = gf_isom_sample_new();p_videosample->data = (char*)malloc(1024 * 1024);p_hevc_config = gf_odf_hevc_cfg_new();p_hevc_config->nal_unit_size = 32 / 8;//gf_isom_avc_config_new(p_file, m_videtrackid, p_config, NULL, NULL, &i_videodescidx);//gf_isom_set_visual_info(p_file, m_videtrackid, i_videodescidx, width, height);//初始化配置gf_isom_hevc_config_new(p_file, m_videtrackid, p_hevc_config, NULL, NULL, &i_videodescidx);gf_isom_set_nalu_extract_mode(p_file, m_videtrackid, GF_ISOM_NALU_EXTRACT_INSPECT);gf_isom_set_cts_packing(p_file, m_videtrackid, GF_TRUE);HEVCState hevc = { 0 };m_slotsps = { 0 };m_slotpps = { 0 };m_slotvps = { 0 };m_spss = { 0 };m_ppss = { 0 };m_vpss = { 0 };p_hevc_config->configurationVersion = 1;//Config vpsint idx = gf_media_hevc_read_vps((char*)vps, vpslen, &hevc);hevc.vps[idx].crc = gf_crc_32((char*)vps, vpslen);p_hevc_config->avgFrameRate = hevc.vps[idx].rates[0].avg_pic_rate;p_hevc_config->constantFrameRate = hevc.vps[idx].rates[0].constand_pic_rate_idc;p_hevc_config->numTemporalLayers = hevc.vps[idx].max_sub_layers;p_hevc_config->temporalIdNested = hevc.vps[idx].temporal_id_nesting;m_vpss.nalus = gf_list_new();gf_list_add(p_hevc_config->param_array, &m_vpss);m_vpss.array_completeness = 1;m_vpss.type = GF_HEVC_NALU_VID_PARAM;// naltype = VPSm_slotvps.id = idx;m_slotvps.size = vpslen;m_slotvps.data = (char*)malloc(vpslen);memcpy(m_slotvps.data, vps, vpslen);gf_list_add(m_vpss.nalus, &m_slotvps);//Config spsidx = gf_media_hevc_read_sps((char*)sps, spslen, &hevc);hevc.sps[idx].crc = gf_crc_32((char*)sps, spslen);p_hevc_config->profile_space = hevc.sps[idx].ptl.profile_space;p_hevc_config->tier_flag = hevc.sps[idx].ptl.tier_flag;p_hevc_config->profile_idc = hevc.sps[idx].ptl.profile_idc;m_spss.nalus = gf_list_new();gf_list_add(p_hevc_config->param_array, &m_spss);m_spss.array_completeness = 1;m_spss.type = GF_HEVC_NALU_SEQ_PARAM;// naltype = SPSm_slotsps.id = idx;m_slotsps.size = spslen;m_slotsps.data = (char*)malloc(spslen);memcpy(m_slotsps.data, sps, spslen);gf_list_add(m_spss.nalus, &m_slotsps);int act_width = hevc.sps[idx].width;int act_height = hevc.sps[idx].height;//Config ppsidx = gf_media_hevc_read_pps((char*)pps, ppslen, &hevc);hevc.pps[idx].crc = gf_crc_32((char*)pps, ppslen);m_ppss.nalus = gf_list_new();gf_list_add(p_hevc_config->param_array, &m_ppss);m_ppss.array_completeness = 1;m_ppss.type = GF_HEVC_NALU_PIC_PARAM;// naltype = PPSm_slotpps.id = idx;m_slotpps.size = ppslen;m_slotpps.data = (char*)malloc(ppslen);memcpy(m_slotpps.data, pps, ppslen);gf_list_add(m_ppss.nalus, &m_slotpps);gf_isom_set_visual_info(p_file, m_videtrackid, i_videodescidx, act_width, act_height);gf_isom_hevc_config_update(p_file, m_videtrackid, 1, p_hevc_config);//销毁申请的内存资源gf_list_del(m_vpss.nalus);gf_list_del(m_spss.nalus);gf_list_del(m_ppss.nalus);free(m_slotvps.data);free(m_slotsps.data);free(m_slotpps.data);p_hevc_config->param_array = NULL;return true;
}

结合写MP4篇我们不难看出,MP4BOX对H265专门封装了个结构函数gf_isom_hevc_config_new()用以对H265参数的设置,设置方法和H264相似,不过对H265处理更加细致,MP4BOX将VPS,SPS,PPS的各个参数拆分出来进行赋值,通过gf_isom_hevc_config_update写入解码参数信息。
注意: H265写MP4尚未加入最新的SkeyePlayer源码,这里是独家首发哦–!(主要是忘了,会在近期加入)

获取更多信息

邮件:support@openskeye.cn
WEB:www.openskeye.cn
QQ群:102644504

SkeyePlayer源码解析系列之支持H265相关推荐

  1. SkeyePlayer RTSP/RTMP流媒体超低延迟播放器源码解析系列之H264一帧多NAL写MP4录像花屏问题解决方案

    接上一篇[SkeyePlayer源码解析系列之录像写MP4]之续篇,我们来讲解一下关于H264编码格式中的一帧多nal(Network Abstract Layer, 即网络抽象层),关于H264和N ...

  2. prometheus变量_TiKV 源码解析系列文章(四)Prometheus(下)

    本文为 TiKV 源码解析系列的第四篇,接上篇继续为大家介绍 rust-prometheus.上篇主要介绍了基础知识以及最基本的几个指标的内部工作机制,本篇会进一步介绍更多高级功能的实现原理. 与上篇 ...

  3. openGauss数据库源码解析系列文章——openGauss开发快速入门(二)

    在上一篇openGauss数据库源码解析系列文章--openGauss开发快速入门(上)中,我们介绍了openGauss的安装部署方法,本篇将具体介绍openGauss基本使用. 二. openGau ...

  4. openGauss数据库源码解析系列文章--openGauss简介(一)

    openGauss数据库是华为深度融合在数据库领域多年经验,结合企业级场景要求推出的新一代企业级开源数据库.此前,Gauss松鼠会已经发布了openGauss数据库核心技术系列文章,介绍了openGa ...

  5. Redux 源码解析系列(一) -- Redux的实现思想

    文章来源: IMweb前端社区 黄qiong(imweb.io) IMweb团队正在招聘啦,简历发至jayccchen@tencent.com Redux 其实是用来帮我们管理状态的一个框架,它暴露给 ...

  6. TiKV 源码解析系列文章(二)raft-rs proposal 示例情景分析

    作者:屈鹏 本文为 TiKV 源码解析系列的第二篇,按照计划首先将为大家介绍 TiKV 依赖的周边库 raft-rs .raft-rs 是 Raft 算法的 Rust 语言实现.Raft 是分布式领域 ...

  7. Tomcat源码解析系列二:Tomcat总体架构

    Tomcat即是一个HTTP服务器,也是一个servlet容器,主要目的就是包装servlet,并对请求响应相应的servlet,纯servlet的web应用似乎很好理解Tomcat是如何装载serv ...

  8. TiKV 源码解析系列 - Raft 的优化

    这篇文章转载TiDB大牛 唐刘 的博客:https://mp.weixin.qq.com/s?__biz=MzI3NDIxNTQyOQ==&mid=2247484544&idx=1&a ...

  9. Netty 源码解析系列-服务端启动流程解析

    netty源码解析系列 Netty 源码解析系列-服务端启动流程解析 Netty 源码解析系列-客户端连接接入及读I/O解析 五分钟就能看懂pipeline模型 -Netty 源码解析 1.服务端启动 ...

最新文章

  1. ISA Server 2004 中的导出、导入和备份功能
  2. 彩色圆圈的html代码,HTML5 Canvas彩色圆点粒子飘动动画特效
  3. uestc 250 windy数(数位dp)
  4. 1.1 Java流是什么?输入/输出流又是什么?
  5. Mysql删除语句优化_MySQL性能优化之常用SQL语句优化
  6. C#一元运算重载的深入理解
  7. linux 字符串加入中括号,方括号及其在命令行中的不同用法介绍
  8. sql每个月每个人的花销占比_11月:每个认真生活的人,都值得被认真对待
  9. [笔记]前端 - 下拉菜单的实现
  10. thinkphp 手机号和用户名同时登录
  11. JAVA线程池shutdown和shutdownNow的区别
  12. 在Ubuntu Kylin 优麒麟系统中安装 Etcher镜像烧录软件
  13. NEFU OJ 574 丑数
  14. Android Databinding 与 RecycleView mvvm的运用
  15. 郑厂长系列故事——排兵布阵 状态压缩DP
  16. android html5播放器,用 HTML5 播放器在 iOS 或 Android 等移动设备上播放视频
  17. angular中的 :host 、:host-context、::ng-deep
  18. Unity tips 之文字动画效果
  19. Java-String的用法
  20. 【AUTOSAR-CP-CAN-2】AUTOSAR COM

热门文章

  1. skewx 字体模糊_为什么网站设计宋体消除锯齿要用无,而其他字体如黑体用平滑,还有英文字体要用那种消除锯齿方式?...
  2. Hive HBase
  3. STM32CubeIDE XiP 和 BootROM介绍, XiP外部内存QSPI FLASH执行用户代码
  4. 印度乘法口诀双位数乘法详解
  5. Speedoffice(word)如何生成目录
  6. 小米为什么不怕iPhone降价?
  7. Java web系统打包成exe安装文件
  8. 解析鸿峰智能软件-------恒指
  9. 校园跑腿的优势和劣势
  10. ios 仿电脑qq登录界面_iOS开发UI篇—模仿ipad版QQ空间登录界面-阿里云开发者社区...