前情提要
什么是HEVC、苹果为什么选择HEVC,之前在503 hevc introduce已经介绍过了,总的来说就是为了4K甚至更大的分辨率、高比特深度(high depths)比如10-bit、更宽的色彩空间,做这些都需要更高的压缩率来支撑,hevc相对H.264压缩率提高了40%,对于相机采集的场景,相比H.264压缩率提升了50%
今天讲的主要是如何应用,分四个主题:
- Access
- Playback
- Capture
- Export
Access
PhotoKit的PHImageManager请求HEVC内容播放
|
|
PHAssetResourceManager请求HEVC的Assets,可访问对应文件,进行转码
|
|
用PHAssetResourceManager访问HEVC数据
|
|
Playback
- HEVC支持AVKit、AVFoundation、VideoToolBox
- HEVC支持HLS、支持边下边播、本地文件播放
- MP4,MOV容器格式支持
- API是适配好的,不用修改调用点
播放一个Video的API对于H.264和HEVC是一样的:
|
|
对于解码能力,想要判断iOS系统是否支持解码一个assetTrack用这isDecodable属性
|
|
对于播放能力,用isPlayable属性,不是所有内容都能实时播放、不同设备能力不一样
硬解码可以得到较好的功耗、最好的解码性能,下面这句用于判断是否具备硬解能力,这个API不光可以用于判断HEVC,对于其他Codec也适用
|
|
我们该如何选择Codec,H.264还是HEVC?如果你更重视全平台兼容性,选H.264,这东西有十年了,被产业广泛接受,第三方库对他的兼容性很好,如果你要更小的文件或更高的清晰度,选HEVC
Capture
接下来看看Capture,我们可以用AVFoundation来采集HEVC视频,视频容器格式支持mp4,mov,最低配需要A10 CPU也就是iPhone 7
我们看看大家比较熟悉的结构图
这个图不再赘述,录制H.264 4K视频的常规代码如下
|
|
录制HEVC怎么做呢?
|
|
采集HEVC的LivePhoto,结构与视频类似,但Output换成了AVCapturePhotoOutput
我们先看看LivePhoto有哪些更新:
- 视频稳定性,播放时画面不再颤抖
- 在播放LivePhoto时候不再停掉音乐播放
- 更加流畅,帧率支持30fps
录制HEVC的LivePhoto
iOS11提供了新API判断是否支持某个codec,如果不做这个判断赋值,默认是.hevc
|
|
下面是定制性较强的采集流程图,用到了AVAssetWriter,可以想加一层滤镜处理。
配置AVAssetWritterInput有两种方式,建议用新API
|
|
Export
导出和转码HEVC
- AVFoundation和VideoToolbox支持转码到HEVC
- mp4、mov容器格式支持
- 需要条件判断的代码支持(iPhone7+才能HEVC encode)
对于AVAssetExportSession苹果引入了三个新的Preset,可将其他codec比如H.264转码成HEVC,以节省存储空间
|
|
AVAssetExportSession是更高层的类,向下一层用AVAssetWriter来做转码的话,用下面两种方式来设置Preset
|
|
不是所有编码器都能适配你的Output Settings选项,需要用一个函数验证你的输出是否支持你设置的编码codec,encoderID和properties是验证后OK的编码器ID和合法的输出设置选项
|
|
向下一层来看VideoToolBox相关的编码API,最只要的类是VTCompressionSession
高级主题:
Bit Depth
日出日落在现实中和在电影里是不尽相同的,比如左边的渐变是电影中我们看到的渐变的细节,现象的原因是没有足够的精度去表达这个渐变色阶梯间的细微差别,而10-bit编码可以做到这样的精度,色彩渐变更细腻
10-bit的另一个好处是相比8-bit更加节省存储空间,session没提 http://x264.nl/x264/10bit_02-ateme-why_does_10bit_save_bandwidth.pdf
对于8-bit和10-bit 的支持多说一句,在 iOS 平台, 8-bit的 HEVC 硬编码需要至少 A10 Fusion 芯片才能支持;而iOS的 10-bit HEVC 硬编,由于现有iPhone CPU Level不够,估计要等 A11 芯片了,猜测也许10月的iPhone 8;而 8 位及 10 位 HEVC 的软编码则是MacOS全平台支持。HEVC的硬编/软编的平台支持和8/10-bit的平台支持这个session说的有点乱,请没看[Session 503]的同学直接看下面总结的表格会清晰点:
HEVC Encode Support
- 8-bit hardware encode: iOS devices with A10 Fusion chip and over | macOS devices with 6th Generation Intel Core and over
- 10-bit software encode: All Macs running macOS
HEVC Capture Support - 8-bit hardware encode: iOS devices with A10 Fusion chip and over [iPhone 7 Plus, iPhone 7, 10.5-inch iPad Pro, 12.5-inch iPad Pro (2017)]
HEVC Decode Support
- 8-bit hardware decode: iOS devices with A9 chip and over | macOS devices with 6th Generation Intel Core and over
- 10-bit hardware decode: iOS devices with A9 chip and over | macOS devices with 7th Generation Intel Core
- 8-bit software decode: All iOS devices | All Macs
- 10-bit software decode: All iOS devices | All Macs
那么如何设置10-bit呢?通过设置这个选项kVTCompressionPropertyKey_ProfileLevel 为 kVTProfileLevel_HEVC_Main10_AutoLevel,注意检查是否支持10-bit
|
|
Hierarchical Frame Encoding
视频帧基本类型介绍,I帧关键帧,P前向参考帧,B双向参考帧,I P B越往右压缩率越高,计算开销越大,这方面基础知识一大堆,想深入了解的同学自己搜搜就好,不再赘述
现在假设我们有个只能解码30fps的解码器,但有个240帧的内容(李安的比利林恩中场战事120帧。。),很显然,这个假想的解码器必须丢帧,丢帧原则是丢掉其他帧“不参考的帧”,比如IPPPPP这种帧序列可以丢最后一个P帧,没有帧参考他来做diff,同理也可以丢B帧。
我们来看一个低端设备播放240fps内容的真实案例,每隔7个“droppable frame”有一个“Non-droppable frame”(droppable 和 non-droppable可理解成I/P帧的泛化),为了实现droppable frame可丢,让他们全都参考non-dropable frame,而不是他们之间相互参考,但这么做压缩率掉下去了,原因是不能参考邻居,导致每个drappable frame diff出的大小太大不省空间, 这是问题之一
由于假想的解码器只能解30fps的内容,开始丢尝试丢帧,每8个丢4个Droppable,丢到120fps
然后解码器说还是解不了那么多,继续丢到60fps
No way,继续减半丢到30fps
然而,这样的结构,我们很难决策是间隔的丢、还是丢前一半或者后一半。
解决办法是引入时间等级(temporal level),方便我们决策哪些帧先被丢掉比较合适,这样就可以一个level一个level的丢了,就不用纠结怎么丢的问题了;
再来看参考关系,是不是更紧凑了,不是都参考non-dropable了,也会参考邻居droppable了,diff变小提高了压缩率
PS: 隐含的是High Level只参考 low level
同样面对只支持30fps的decoder开始丢帧,过程就变成了丢level
总结下分层编码解决了什么问题:
- 优化了时间伸缩策略,播放丢帧时候不再用猜
- 优化了运动补偿,使得参考关系变得更紧凑,提高了压缩率
- 还有下面这个,没查到什么意思,谁了解给科普下。
怎么用分层编码呢?设置两个属性
|
|
继续举上面的🌰,
kVTCompressionPropertyKey_BaseLayerFrameRate设成30,kVTCompressionPropertyKey_ExpectedFrameRate设置成240,
BaseLayer是必须被解码的关键Level,然后其他Level是用来解码或者丢弃的,level从高到低的丢。
HEIF的正确打开姿势
HEIF的发音
德国人🇩🇪读Hife,法国🇫🇷读Eff,俄罗斯🇷🇺读Heef,
但诺基亚的研究人员对这个标准贡献最大,所以为了尊重他们,读成芬兰🇫🇮口音Hafe,尽管他们是少数
Why HEIF
- 画质一样情况下,HEIF比起20多岁的JPEG平均小两倍,注意是平均,不是最多小两倍
- HEIF允许将图片分成若干区块,这对于大图的增量式解码很有帮助的,这里提到的区块(Tile)千万别和503中讲的CTU(coding tree unit)搞混了,Tile是帧内可以独立进行解码的矩形区域,包含多个按矩形排列的CTU(数目不要求一定相同),再形象点,Tile相当于华容道棋盘,CTU相当于华容道棋子,棋子大小不一定相同,定义这一结构的初衷是增强编解码的并行处理性能,类似高德地图或浏览器加载大网页的懒加载技术。
- HEIF支持了透明度、对比度、深度信息,现代图像格式优势尽显
对于iPhone 的双摄像头如何感知深度,session未涉及,但大体原理其实是很简单的光学+几何原理,下面从知乎盗张图,上过初中的你一定能看懂,图中两个假象的摄像头距离越远测距精度越高,高大上的VR技术以此为前提
深度信息可以辅助做很多图片编辑的效果,比如下面的背景部分加了Noir black and white filter
原图
前景加了fade filter,讲师说小女孩的裤子还是粉色没变,像素眼们怎么看
还可以这样,小女孩手里的花景深最小,保持原有色彩,其他深度部分都黑白
也可以控制前景背景的亮度,比如下图,要下雨了,小女孩还在诡异地笑
苹果工程师还是很诚实的,注意细节,这图绝对特么没P过,绝对Demo跑出来的
想深入了解HEIF的depth map的同学,可以看507、508
HEIF不光支持静态图片,也支持图片序列,比如这种长曝光
为了演示HEIF的超高压缩率和区块懒加载(前文解释过的Tile技术)有多平滑,演讲人展示了在自己iPhone7照片里找了个全景图作为Demo,这个全景图是一个91k by 32k pixels的全景图,如果用RGB色彩空间TIFF容器的图片格式存储这个图片是2GB大小,对于HEIF只有160MB,而且JPEG对图片大小限制在64k by 64k pixels,甚至是不能用来存储这张图的,注意这就是你桌面那个叫Yosemite的破石头山。
为了展示HEIF大小不受限制,而且有分区块的增量加载不会爆掉内存,演讲者一路放大照片,最后看到了照片中山路的限速标志
讲真,这个Demo太东施效颦,和4年前Nokia发布Lumia 1020在草丛里找针的那个Zoom in Demo如出一辙
Low Level Access To HEIF
读写一个图片的最底层的API来自ImageIO的CGImageSource和CGImageDestination,用法和以前读一个JPEG一样,只是换个扩展名,另一个区别是支持硬解的机器6s+确实会解码速度更快一些
|
|
上面Zoom in Demo中提到的分块增量加载,可以从imageProperties一个叫TIFF的sub dictionary找到相关的重要参数,JPEG的metadata没这个信息 :
|
|
如何写HEIC图片文件,同样,把原来的jpg扩展名改成heic,注意用guard let做防御,一旦没有HEVC硬件编码器,destination 是nil,这是唯一能判断当前系统是否可以写HEIC的函数
|
|
HEIF与PhotoKit
Applying adjustments
- Photos
- Videos
- Live Photos
Common workflows
- Display
- Backup
- Share
照片编辑
第一个典型应用场景是读入一个图片、加旋转和滤镜,输出成通用的JPEG,对于读入图片是HEIF类型不用修改代码,PHContentEditingInput是透明的
|
|
第二个典型场景是读入一个视频,无论输入是不是HEVC,编辑并输出成H.264编码的通用视频,透明的。
|
|
编辑Live Photo一样是格式透明的,不管输入是不是HEIF/HEVC,输出会是JPEG/H.264内容
|
|
拍摄HEIF主题
AVCapturePhotoOutput的用法
HEIF拍摄的机型限制:iPhone7+ 和 10.5-inch iPad Pro+(NEW)
上图是AVCapturePhotoCaptureDelegate的处理流程,这个代理能很好支持拍照各个阶段,看上去扩展性可以,后来的迭代中也增加了didFinishRawCaptureFor支持RAW图片格式,也增加了didFinishProcessingLivePhotoMovie用来支持拍摄LivePhoto,但硬伤是iOS4之后一直用CoreMedia中的CMSampleBuffer,CMSampleBuffer可以用来存储各种媒体类型,同样适用于HEVC内容,但和HEIF格式的HEVC内容有本质区别,不支持分区存储增量解码,这样的内容会令解码器迷惑,所以有了新的照片拍摄结果“内存封装类AVCapturePhoto”,用来代替CMSampleBuffer,有如下特点:
- 比CMSampleBuffer更快,可以专门优化他的传输
- 100%不可修改,便于在module间共享
- 专有格式支持
AVCapturePhoto的格式:
|
|
建议逐渐废弃之前AVCapturePhotoCaptureDelegate的老方法,未来会Deprecate
|
|
用以往的方式存一个图片,意味着要大量操作thumbnail和metadata,会伴随很多图片Scale和压缩操作,性能上不是最优的。
新API的处理方式是让你充分定制容器属性,比如可以在AVCapturePhotoSettings指定codec、文件类型,metadata里指定地点,指定缩略图的分辨率,请求后delegate 返回的AVCapturePhoto已经被嵌入到HEIC容器,也包含了你要的那个缩略图,最后调用photo.fileDataRepresentation()的处理相比old way就简单多了,纯NSData到存储的字节copy,没有多余的压缩、缩放操作,尤其存HEIF图会充分释放性能优势
HEVC性能须知:
1.如果一边在录制HEVC视频一边在拍HEIC照片的话(这例子感觉有点极端啊,比如一个直播推H.265,一边在拍照。。WTF),HEVC硬件编码器会很忙,如果你拍的是4K 60fps视频,编码器会忙出翔,他只能优先处理对实时性有更高要求的视频拍摄,你的拍照Request会滞后;且为了保证你拍摄高清视频的帧率deadline,在你请求拍照时只能分出很少的时间片来处理你的请求,HEIF的高压缩率是有时间成本的,这时候需要空间换时间,Trade off掉20%的压缩率,换尽快callback你要的照片,如何解决这个极端场景的问题?可以在拍照里设置用JPEG,HEVC编码器就清净了。
2.还有个极端场景是长曝光,长曝光输出的不是Video,是序列帧图片,而且有每秒10帧的基线要求,对编码器处理效率要求很高的,HEIF格式勉强可以cover 10fps的序列帧(序列帧中图像应该是相互独立的,无帧间预测,他的10fps可以理解成每秒10个关键帧),但帧率要求更高的话就切回JPEG吧。