H.265在花椒直播中的应用与优化

本文由花椒直播技术总监唐赓在LiveVideoStack线上交流分享中的演讲内容整理而成。在分享中,唐赓详细介绍了花椒直播在两年多的H.265线上实践过程中所积累的一些经验,以及一些成本、效果优化方案。

文 / 唐赓

整理 / LiveVideoStack

直播回放

https://www2.tutormeetplus.com/v2/render/playback?mode=playback&token=b605af2653524d909f024109ac628067

大家好,我是来自花椒直播的唐赓。接下来我将借本次分享与大家一起探索H.265在直播中的应用及方案优化。我从2015年加入花椒直播开始便一直分管花椒在移动端领域的基础技术与创新技术,H.265也是其中一个很重要的部分。

1. H.265相对于H.264的主要改进

首先简单介绍一下相对于H.264,H.265的主要改进:

  • H.265使用CU/PU/TU代替H.264上的宏块,使其拥有更多优化选择,提升画质的同时明显降低码率;
  • 取消了隔行扫描模式从而进一步简化设计;
  • 支持35种模式的帧内预测,相对于H.264的9种有明显的提升;
  • 增加TMVP时域运动矢量预测功能以提高画质与压缩率;
  • 增加了TILE与WPP等并行工具集以提高编码速度;
  • 在H.264的Deblock基础上增加了可提升画质的SAO滤波;

观察上图左侧图我们可以看到,H.264的宏块大小十分密集且其中包含许多冗余部分,如右下角的暗处画面浪费如此多的宏块对其进行描述,无疑造成编码资源的浪费与带宽利用的低效;H.265主要面向4K甚至8K等高分辨率应用场景,高分辨率意味着像素密度更大,其颜色变化趋于平缓。如果仍使用传统H.264的宏块策略借助平均而密集的宏块描述画面,显然会造成带宽与码流资源的浪费。

于是H.265引入了自适应宏块划分,按照画面分析得出的数据找到更高效的宏块布局方式,在画面细节较少的部分使用较大面积的宏块从而精准模拟画面中物体元素的轮廓,更加高效地利用现有码流与带宽资源。有很多工具可以显示H.265码流里的分块划分方案,也有开源版本,可在github上找到。此工具可以显示经过自适应宏块划分之后的画面(PPT上只是示意图,不是实际划分方案)。

得益于以上提及的各种优化措施以及其它众多改进措施,H.265比H.264可节省30%~50%的带宽消耗。但在实际应用当中H.265的终端设备普及程度远不如H.264,接下来我为大家分享的绝大多数技术优化方案基本上都是为了弥补H.265在终端设备普及率上的劣势。

H.265在花椒直播上的画质表现可以说很直观地显示了H.265的技术优势:上图左侧是一个基于H.264 1500k进行的直播画面,右侧则是基于H.265 1100k,似乎两个画面并没有很大区别……

但当我们放大画面之后就可以看到左侧女主播的头发细节相对于右侧女主播的更加模糊,这还是在左侧画面的码率更高的基础之上,可以说H.265对于画面质量的提升显而易见。

2. 移动端:软解vs硬解

从2017年初决定上线H.265以来,花椒直播一直希望先找到移动端的解码方案。我们在2016年底通过测试发现FFmpeg的性能已经足够出色,在当时的iPhone 6S上仅需30%左右的双核CPU性能就可完成对H.265文件的流畅软解,这无疑是令人振奋的。所以2017年上半年开始花椒移动端(iOS和安卓)的播放器陆续开始加入对H.265的软解支持,这个阶段同时也开始提供H.265的软编支持,因为硬解方案是在iOS 11正式推出之后才陆续被支持;Android平台则是从5.0 API 24开始陆续支持H.265,之后才逐步有厂商开始支持,由于其碎片化特性,硬编支持的普及程度至今还远逊于iOS。

3. 移动端:软编vs硬编

花椒从2017年上半年加入H.265的解码支持后,同时上线了H.265软编版本(显然没有编码器生成H.265内容解码器也就没什么存在价值了);2017年初,在对H.265软编进行灰度测试时我们发现了一些问题,例如尽管上线之初H.265软编无论是CPU消耗之少还是编码效果之佳都出乎我们的意料,但随着直播编码时间的延长,手机发热的问题也逐渐凸显,过热的手机温度会导致CPU主动降频,算力下降而算力需求不变,软编的CPU消耗占比显著提升,直接造成直播画面的卡顿与丢帧等问题。当时我们收到了一些用户关于此问题的负面反馈,不得已只能停止灰度测试。终于在2017年Q4,苹果WWDC 2017上介绍过的iOS11正式推出,提供了对H.265硬件编码的支持,我们认为时机已经成熟于是立即投入开发了iOS端的硬件编码功能,并在半个月之后上线正式版;与此同时我们也在逐步增加Android平台的H.265硬编支持,包括图中标注的几十款手机我们都能够良好兼容其硬件编码功能。

4. 各个平台的H.265实现方案

下面按照平台划分简介H.265的具体实现方案。iOS是在它的VideoToolBox API中提供了HEVC编码支持, 此API其实很早就已经包含H.265支持,只是一直是苹果私有API,用于苹果自家的FaceTime App中,iOS11后苹果将其开放给所有开发者;Android平台则通过MediaCodec框架实现H.265,PC端则是基于FFMPEG或NVIDIA生成H.265内容。服务端实现H.265的方案有很多,如FFMPEG、NVIVDIA与英特尔以及其它一些公司的定制硬件等。

4.1 iOS HEVC API

上图代码展示了VideoToolBox的使用流程,其中红色字体代表关键参数:首先创建一个CompressionSession并选定CodecType(图中选择HEVC)以及后续的CallBack参数,其次通过字典设置MaxKeyFrameInterval与AverageBitRate,这时视频的GOP与帧率就设置好了;接下来我们调用VTCompressionSessionEncodeFrame,其中的最关键参数是ImageBuffer----每一帧图像数据采集之后都会由ImageBuffer承载,这里我们需要设置PTS也就是帧的显示时间;设置完成之后我们调用VTCompressionSessionEncodeFrame并在其内部编码完毕之后获得回调,所得CMSampleBufferRef就包含编码压缩过的数据;最终我们通过CMBlockBufferRef block=CMSampleBufferGetDataBuffer(sampleBuffer)得到其block长度就可以拷贝得到数据,包含已经完成编码的码流。最后需要我们做的就是将此码流经过一定额外处理之后推送至服务端,整个流程结束。

4.2 Android

Android 平台的API的使用流程类似,MediaFormat 用于封装宽度、高度、编码码率、缓冲格式、帧率、关键帧等参数,随后在mEncoder = MediaCodec.createEncoderByType选择“video/hevc”作为编码类型;接下来mEncoder.configure通知Encoder创建用于编码器MediaCodec.CONFIGURE_FLAG_ENCODE,之后调用mEncoder.start()即可开始编码。相机获得的输入在Texture里,输入之后, handleFrameAvailable获得 Encode结果,getOutputBuffers可获取ByteBuffer 数组,数组里头,实际上就是一个个的ByteBuffer数据,直接就把这些数据输出就OK了,所以这个也是一个比较简单的一个过程。

4.3 PC FFmpeg

PC端基于X265的FFMPEG拥有上图展示的部分主要优势:FFMPEG的开源特性使其使用成本明显降低,同时也可达到令人满意的生成质量;除此之外,其代码升级较为及时,同时可灵活定制的特性也使其应用更加广泛。当然它也有一些不足与缺陷,如CPU处理速度较慢等。

上图代码展示了PC FFMPEG(X265)的大致流程,仅展示了较为重要的几行,实际可运行的代码可以看到在代码量上相对于iOS与Android版本有明显提升。首先,avformat_alloc_output_context2中给定了一个“output.hevc”作为输出,随后再将“output.hevc”给定avio_open用以根据扩展名判断应当使用什么编码器;video_st = avformat_new_stream会创建相应的视频流;接下来的pCodecCtx除了指定Codec(pCodecCtx = videost->codec;)与Media类型(pCodecCtx->codectype = AVME-DIA_TYPE_VIDEO),也会指定如输入像素格式、宽度、高度、帧率、码率、GOP大小等参数,同时指定Codec ID为HEVC;接下来的pCodec = avcodec_find_encoder(pCodecCtx->codecid)就会寻找相应的codec ID并初始化对应编码器从而获取pCodec, pCodec = avcodec_find_encoder(pCodecCtx->codecid),最后avcodec_open2(pCodecCtx, pCodec, &param)完成初始化;随后的picture = avcode_calloc_frame(); 获取到此帧的数据结构之后会填充Picture也就是avpicture_fill,之后通过av_write_frame输出为指定容器的文件。这里只是大致描述FFMPEG(x265)编码的一个过程。

4.4 PC NVIDIA

PC端的NVIDIA SDK具有硬件编码性能强悍但价格十分昂贵的特点;家用的游戏卡有限制,只能并发进行两路编码任务;相同画质下编码输出的码率偏高;API非常简洁,实际使用中其代码量也明显减少。

以CUDA版本为例,首先cuDeviceGet(&cuDevice, 0);用以获取所使用的GPU设备,随后的cuCtxCreate(&cuContext, 0, cuDevice);创建上下文;完成上下文创建之后,NvEncoderCuda 会创建相应对象并输入宽高以及编码格式,编码器初始化完成;接下来就是数据读取、调用、写入、输出等常规流程的循环。

4.5 SERVER FPGA/ASIC

除了上述方案,还有一些基于硬件定制的解决方案如FPGA、ASIC定制硬件等。通常他们都提供ffmpeg的插件,所以开发过程和上面ffmpeg的开发过程类似。需要注意的是由于现在相关领域的厂商已经不少,我们需要详细对比不同的解决方案。FPGA普遍能达到60~240FPS 1080P的性能,支持B FRAME、SCENE CHANGE DECTECTION、自适应量化等算法;同时,Scale运算的性能也是选择方案的主要参考因素之一。有的解决方案板子上的逻辑电路主要用于编码,Scale能使用的运算单元不够多导致需要改变分辨率的场合下性能稍差,而如果Scale使用过多运算单元那么编码能使用的运算单元也会偏少,此时如何对有限运算资源进行有效分配成为摆在厂商面前一个需要仔细权衡的问题,从客户的角度来说应当根据自己产品使用场景重点考核一下各家的解决方案,进行充分的测试。

5. H.265高清低码方案

接着再说说我们在 H.265的高清低码解决方案方面的探索,我们有如下思路

其原理是基于CUDA/CNN的ROI生成算法,通过我们自己训练的一套卷积神经网络分析生成用户兴趣热点区域并基于此兴趣热点科学分配码率。

如上图输入主播直播画面,输出的是前景轮廓,这就是我们计算出来的用户在观看直播画面时最感兴趣的视觉重点区域。将此结果输出给编码器,编码器就可根据用户视觉重点区域的分布情况合理分配码率,提高前景区域的码率,降低背景码率,从而确保观众能够在视觉核心区域获得最好的观影质量。

上图展示了我们此项探索的成果,尽管画面整体看上去似乎没有多大区别,但如果放大非前景区域画面可以观察到细微的画质损失,而前景区域没有不同,达到保证用户的视觉焦点上画质不降低而整体码率降低的效果。

再简单介绍一下我们的一个Cuda优化细节,大家在做类似功能的时候可以借鉴一下类似想法。早期技术demo阶段我们主要使用OpenCV实现ROI前处理,包括Scale、色彩空间变换等,发现性能远达不到上线要求,急需改进。

进一步优化CUDA性能是我们接下来需要重点解决的关键问题

分析发现,完全依赖CPU进行计算的OpenCV确实存在很多问题:首先Decode输出的数据经过YUVtoRGB转换后进行Scale处理(因为CNN对输入数据的大小有限制),这一步非常消耗CPU时间;之后数据由CPU主内存拷贝至GPU显存交给Caffe CNN处理,这一步也消耗大量总线传输时间。改进的第一个版本是由GPU来完成色彩空间转换与Scale, 完成处理的数据会进行一次显卡上的数据复制,复制到Caffe CNN输入缓冲区,交给Caffe CNN处理。

继续研究我们发现,Caffe CNN 本身也是由CUDA的API编写,可以获得其设备输入缓冲区的指针。于是我们将YUVtoRGB与Scale两项转换整合成一段代码,并使其输出结果直接写入Caffe CNN的输入缓冲区从而减少GPU上的一个内存拷贝,通过上述改进获得了明显的处理速度提升,达到了上线要求。

我们期待的优化结果是将尽可能多的处理流程整合并转移至GPU,如果将 Decode功能也转移至GPU,相信会带来质的性能提升。

6. H.265高清低码尝试

实际开发中我们使用了NVIDIA VISUAL PROFILER来进行优化,接下来介绍一下优化过程。

用Profiler运行过程序后,生成一个类似上图的视图,列举了许多kernel的性能数据,因为我们用了Caffe,可以看到其中大部分是Caffe的函数,也可以找到自己开发的kernel函数。通常情况下YUVtoRGB会得到byte类型的输出,而Caffe CNN则要求需要输入浮点数,因此我们将YUVtoRGB(含float转换)与scale全部整合至一个kernel中。

上图中的Occupancy->Achieved利用率为62.9%,可以说基本符合我们的预期与需求。

7. WEB端H.265播放解决方案

之前说的H.265解决方案主要运行在移动端,下面再说说我们在WEB端上的尝试。我们以FLASH的H.264解码器消耗单核25%的能力作为基准,进行了如下探索。

首先调研WEB端H.265播放解决方案的原生支持情况,如上图所示,红色代表不支持,黄绿色代表有条件的部分支持,其中的绿色所在iOS端的Safari浏览器,对WEB端H.265播放解决方案的支持情况良好。

参考网上的数据,Flash方案使用ACTION SCRIPT加载FlashCC,所生成的SWC可达到1080P 10FPS;而JS方案的相关代码LIBDE265.JS可以在网络上找到,使用LIBDE265.JS播放480P可以维持帧率在40FPS以上,CPU 使用率大约为110%到140%。

继续测试WASM解码器,CPU单核占用为50%~60%,解码720P 直播流可达到15FPS的帧率; 厂商宣称1080P可以达到40FPS的处理速度。因为用到了WASM,浏览器需要至少不低于上图列举的版本。

综合以上各种评测,我们选择了WASM方案,目前我们WASM版本的解决方案已经正式上线运行,效果良好。

8. 成本考虑与优化方案

实施H.265的最大动机是它能显著节省流量成本。理论上H.265使用越多流量成本节省越多,但另一方面来看,企业付出的成本还包括采购或自研编解码器所带来的转码成本以及购买转码服务器、GPU、FPGA/ASIC在内的硬件成本,这些都是需要企业综合考虑投入产出比、制定合理的流量分配方案。从节省成本的角度来看,自研的采购成本会低许多;同时硬件也不是性能越强越好,而是需要结合应用场景与产品特点综合考量选择最合适的硬件组合以更高效地利用资金并收获理想效果。

接下来我们将着手探索如何优化成本。

传统的成本优化方案如上图展示的那样,引入265上行推流,给支持265的终端直接提供265码流,但为了保证Web/H5等只支持264的终端正常观看,需要提供对应的264码流(服务端实时进行265到264转码) 。

降低成本的第二步,引入高清低码方案,在服务端进行264/265的高清低码转码。

将H.264转为ROI H.264或ROI H.265可明显节省成本。

如果PC推流端性能足够强大,例如配置了专业显卡,可以执行更多的边缘计算任务,可以尝试直接输出H.264与H.265两路流,从而不需要在云端转码。

更激进的方案是在以上几种上行推流状态中动态切换。通过实时统计一路流的拉流数目,选择性价比最高的方案。当然此策略需要播放器业务端的良好支持,流切换一定需要科学高效,避免卡顿。同时切换也不能过于频繁,以避免对用户观看体验造成不良影响。

原文发布于微信公众号 - LiveVideoStack(livevideostack)

原文发表时间:2019-04-15

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券