熟悉webrtc都知道:谷歌的webrtc,默认不支持h265,毕竟涉及到很多专利的事宜,这中间的八卦就暂时不做探究。但是今天拿到一个IPC,该设备会发送H265数据,如下图所示,要做到兼容相关IPC,只能适配H265编解码了。所以最近熬秃了好几把头发就做了一下相关知识的学习,以下是自己的学习笔记,提供大家一个解决问题思路,由于iOS刚接触,同时H265调试经验也欠缺,很多都是一边查找资料,一边学习的,难免有处理不妥当之处,欢迎一起讨论学习,开发大神请绕行。
补充1 H265编码特性
学习之前先了解一下H265的编码特性,有的放矢才能遇到问题及时排查。H265的经典编码框架如下图:
HEVC 的编码框架是在H26X的基础上逐步发展起来的,主要包括变换、量化、熵编码、帧内预测、帧间预测以及环路滤波等模块。相关内容推荐阅读万帅老师的书《新一代高效视频编码H.265/HEVC:原理、标准与实现》。
一般提到H265,都难免要和H264对比一番,以下是这次需求端要求适配H265的基本理论依据,如下表格:
理论上要直接适配一种编解码格式,肯定要学习相关理论,然后再做对应适配。然而时间紧张,任务繁杂,根本没有足够时间积累。想要有一个全面直观的认识,同时为了验证IPC是否正常,所以首先用VLC进行播放尝试:PC端确认该IPC可以通过 VLC进行监控,这时我们可以通过wireshark抓包,拿到第一手数据进行分析。通过抓包可以看出:1920*1080的视频流除了IDR数据帧大小略大,其他数据都是一个NALU单元就完成封装了,文件大小确实小很多。
接下来我们依次认识H265的封包和关键信息。
H265关键信息
通过相关抓包,可以看到整理结构如下图,包含了VPS,SPS,PPS, FU分片包,Trail_R的包等。
(1)VPS结构
VPS(Video Parameter Set, 视频参数集)依据ITUHEVC的标准文档,VPS的参数结构以及每一个条款的解释,很多大佬已经有写明了,HEVC来说自己还是小萌新在此不再累述,可以参考自己关注的一个大佬的博文:
https://blog.csdn.net/Dillon2015/article/details/104142144
其抓包中VPS数据参数如下:
(2)SPS结构
SPS的内容大致包括解码相关信息,如档次级别、分辨率、某档次中编码工具开关标识和涉及的参数、时域可分级信息等。详细介绍说明可以参考https://blog.csdn.net/Dillon2015/article/details/104148026
其抓包中SPS数据参数如下:
(3)PPS结构
HEVC的图像参数集PPS包含每一帧可能不同的设置信息;其内容大致包括初始图像控制信息,如量化参数(QP,Quantization Parament)、分块信息等。即PPS包含了每一帧图像所用的公共参数,即一帧图像中的所有SS会引用同一个PPS;每张图像的PPS可能不同。详细介绍说明可以参考:https://blog.csdn.net/Dillon2015/article/details/104204039
其抓包中PPS数据参数如下:
有了这些数据,至少可以解析出来该IPC支持的分辨率,profile,level等基本数据信息,为初始化做准备。
H265解封包
由上面信息可以知道H265的封包格式和H264基本上保持一致,也是通过NALU单元进行分装,不过不太一样的地方是 H265的NALU Header长度是2个字节,而 H264的NALU Header的长度是1个字节,所以解析时候需要进行移位操作,否则读取数据异常,导致包类型无法辨别(在这里踩了一个小坑,印象深刻)。
H26封包中NALU type主要类型如下图所示。由于不同厂家支持程度不一,本次适配过程中主要关注的几个类型包括:kTrailR(1),kIdrwRadl(1),kVps(1),kSps(1),kPps(1),kFU(1)等。
(1)FU NALU
当 NALU 的长度超过 MTU 时, 用于把当前NALU单元封装成多个 RTP 包,HEVC的FU单元的type值为49,具体组织结构如下:
FU Nalu相关解析代码如下:
(2)Single NALU
单个 NAL 单元数据包只包含一个 NAL 单元,由一个有效载荷头(表示为 PayloadHdr)、一个有条件的16位DONL字段和NAL 单元有效载荷数据。
(3)AP NALU
HEVC封包另外一个种封包格式:聚合模式(Aggregation Packets,APs),主要为了减少小型 NAL 单元的打包开销,例如大多数非 VCL NAL 单元,它们的大小通常只有几个字节。AP 将 NAL 单元聚合在一个访问单元内。AP 中要携带的每个 NAL 单元都封装在聚合单元中。聚合在一个 AP 中的 NAL 单元按 NAL 单元解码顺序排列。
AP Nalu和Single Nalu相关代码解析如下:
当接收到每一个HEVC包之后,一次送到Jitterbuffer中,完成数据帧的重新组装和排序,必要时候进行数据矫正和重传操作,这是另外的技术,此处暂不做讨论。
H265 VideoToolBox解码
收到完整数据帧时候,VCM会依据时间戳一次获取相关数据,送解码器,iOS平台就是VideoToolBox解码了。这部分自己是小白,简单说明关键函数,大佬勿喷。
(1)ResetDecompressionSession
ResetDecompressionSession完成解码参数的构建,以及Session的创建。主要注意iOS支持的色彩是有差别的,设置时候需要明确是否支持,比如这次就设置了kCVPixelFormatType_420YpCbCr8BiPlanarFullRange类型。
同时在reset函数中注册callback函数,用于接收解码完成后的视频帧。
(2)decode
iOS VideoToolBox相关开发说明,可以参考https://devstreaming-cdn.apple.com/videos/wwdc/2014/513xxhfudagscto/513/513_direct_access_to_media_encoding_and_decoding.pdf?dl=1
获取VPS,SPS,PPS相关数据,构建CMVideoFormatDescription相关数据;
(3)callback
callback函数用于接收H265解码数据,并用于送到显示端进行渲染的。
补充2 Annexb和AVCC格式转换
Android的硬解码接口MediaCodec只能接收Annex-B格式的H264数据,而iOS平台的VideoToolBox则相反,只支持AVCC格式。所以要进行一次转换,相关转换规则有大佬做了说明,可以参考https://www.jianshu.com/p/3192162ffda1
补充3 iOS编解码错误说明
在调试iOS编解码过程碰到几个CallBack的错误,于是找了一下相关错误代码,常见的错误如下所示:
其中一个错误kVTVideoDecoderBadDataErr = -12909,该错误找了很多久,也查了很多资料,一直卡住2天,每天早出晚归,熬最深的夜,加最晚的班,我可怜的头发又少了好几根。RIP.
期间尝试各种办法验证确认:
(1)将所有数据包和数据类型打印;
(2)送解码前数据打印和保存;
(3)反复确认VPS,SPS,PPS数据内容;
(4)确认调用流程;
(5)查找githubdemo。
最后确认经过多次反复确认代码,验证裸流,比较大小之后,最后发现计算长度时候H265 NAL头计算错误,导致IDR帧无法正确解码,最后找到问题,一行代码解决问题。其实该问题在RFC7798中有说明,只是自己还是按照H264惯性思维处理,这也是基础知识不扎实的根本原因(不过回想一下好像本来也没有这块知识,惭愧,惭愧)。
题外话:排查问题过程很艰辛,最后一行代码处理完成,这是目前工作中很常见的,所以也是自己给新人,或者要入门音视频强调的一点:保持足够投入度,最好是兴趣驱动。《格局》一书中说:主动做事的收益,或许不会在一两天内显现出来但是长期坚持下来,主动做事的人,就能和其他人拉开距离。
扯远了,最后分享一下排查问题期间也查阅其他人调试过程发现的问题点,在此一起收集一下,方便后来者遇到问题可以快速查阅:
(1)比如,省流模式下解码失败
https://www.cnblogs.com/jiayayao/p/9575186.html
该文章提到一种思路:可以借鉴比较成熟的ijkplayer,对比流程和处理细节,查找得到解决办法。
(2)比如,解码器Session失效问题
https://blog.csdn.net/DittyChen/article/details/70843659
该文章中提到,如果VideoToolBox返回码是 kVTInvalidSessionErr = -12903,也就是说解码器Session异常或者失效了。可以在收到该返回码时调用ResetDecompressionSession操作完成重置,再进行切换时就会正常了。该部分优化已经同步了,防止切换异常;手动狗头,感谢大佬。
(3)比如,annexB与hvcc转换异常问题
https://github.com/bilibili/ijkplayer/issues/4141
该Issue中提到,ios硬解h265 NALU失败,看了代码发现在vtbformat_init中对265从annexB=>mp4转换时需要使用ff_isom_write_hvcc,而不能重用264的ff_isom_write_avcc。两者区别还是比较多的,有兴趣的翻阅一下FFMPEG代码自行补充。
参考文献:
(1)https://www.rfc-editor.org/rfc/pdfrfc/rfc6184.txt.pdf
(2)https://www.rfc-editor.org/rfc/pdfrfc/rfc7798.txt.pdf
(3)https://github.com/open-webrtc-toolkit/owt-deps-webrtc
(4)https://stackoverflow.com/questions/29525000/how-to-use-videotoolbox-to-decompress-h-264-video-stream/29543061#29543061
(5)https://stackoverflow.com/questions/66122770/videotoolbox-hevc-decoding-failing-for-ios14-on-device?rq=1
(6)https://www.cnblogs.com/jiayayao/p/9575186.html
(7)https://blog.csdn.net/DittyChen/article/details/70843659
(8)https://devstreaming-cdn.apple.com/videos/wwdc/2014/513xxhfudagscto/513/513_direct_access_to_media_encoding_and_decoding.pdf?dl=1
(9)https://www.cnblogs.com/jiayayao/p/9575186.html
(10)https://juejin.cn/post/6844904152150048775
(11)https://github.com/open-webrtc-toolkit/owt-client-native
作者:Fenngtun 公众号:MediaStack
版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。