F285925d3cbbcf3c3d7d11cf5c04b036
开发者该知道的 HEVC 与 HEIF

作者:刘栋,博客:https://anotheren.com

1. HEVC 的基本介绍

HEVC 是一种视频编码标准。iOS 中上一代使用的编码标准是 H.264 (视频) / AAC(音频)。

HEVC(High Efficiency Video Coding) 是 AVC(Advanced Video Coding) 演进版本,当然你也可能听过它们一些不同的名字,不过是不同标准化组织的标准编号差异罢了,具体可以看下表:

- ITU-T (国际电信联盟电信标准化部门) ISO (国际标准化组织)
AVC H.264 ISO/IEC: 14496 MPEG-4 Part 10
HEVC H.265 ISO/IEC: 23008 MPEG-H Part 2

在 Apple 给出的数据中 HEVC 能比 H.264 提高 40% 的压缩率,而在实际使用中,以 iOS 自带相机拍摄 4K 视频为例,视频文件体积由 iOS 10(H.264 / AAC 编码)的 350 MB 每分钟下降到 iOS 11(HEVC / AAC 编码)的 170 MB 每分钟,对生成文件的空间占用率优化十分显著。

1.1 Apple 生态中的 HEVC 支持

Apple 现在支持的 HEVC Codec(编解码器)类型为 kCMVideoCodecType_HEVC ,即 hvc1 ,对应的 Profiles 为 Main, Main Still Picture 及 Main 10 (即对应 HEVC Version 1 Profiles 中所有的内容),相应的文件封装格式(容器)还是我们熟悉 QuickTime Movie (.mov) 及 ISO MPEG-4 (.mp4) 。你可以在新的 iOS 11, tvOS 11 及 macOS 10.13 中使用它。

1.2 HEVC 的硬件要求

软编码 /软解码 是指使用 CPU 来完成编解码运算,硬编码/硬解码 是指使用非 CPU 进行解码运算,常见的就是使用 GPU。相对而言,硬编码/硬解码 能够省电、减少设备发热,对于移动设备延长续航有很大的作用。当前如果要在 iOS 10 或者更早设备上解码 HEVC 资源,一般是使用 FFmpeg 这一类的软解码方案(但请注意它的 License 是 LGPL )。

解码 Decode

新系统下所有 iOS / tvOS / macOS 设备均可执行 8-bit / 10-bit 的软解;在 iOS (tvOS) 上至少需要 A9 芯片来执行 8-bit / 10-bit 硬解码(也就是 iPhone 6s 之后的机型,猜测 9 月也会发布带 A9 / 4K 的 tvOS 新机型),而在 macOS 上至少需要酷睿 6 代来硬解码 8-bit 的资源,或者酷睿 7 代来硬解码 10-bit 的资源。

编码 Encode

新系统下,iOS 需要至少 A10 芯片来完成 8-bit 资源的硬编码( iPhone 的相机现在仅支持 8-bit 视频的拍摄,有兴趣可以研究一下使用 AVCaptureVideoDataOutput 来接收相机输出时的 videoSettings);在 macOS 上,至少需要酷睿 6 代来硬编码 8-bit 的资源,而 10-bit 的软编码则可以在所有的 Mac 上完成。

如果你想要查询当前设备是否支持硬件编码,可以使用下面这个新增的方法来查询。但是当前的测试版似乎还是存在 Bug,笔者使用了安装 iOS 11 beta 2 的 iPhone 7 Plus 及 iPad Pro 10.5 进行测试,均返回了 false ,而按照 Apple 给出的资料,应当是可以支持硬解的 🤷‍♂️。

  • // 测试设备 iPhone 7 Plus / iOS 11 beta 2 / Xcode 9 beta 2 / Swift 4
  • let isSupport = VTIsHardwareDecodeSupported(kCMVideoCodecType_HEVC) // false

2. HEIF 基本介绍

HEIF(High Efficiency Image File) 是一种图像文件封装格式。不要惊讶,HEIF 的先行者是沙滩上的诺基亚,你可以在这里找到相关的信息。诺基亚称其为“Future Container Format for Images and Image Sequences.”。回想 Live Photo 这种形式也并不是由 Apple 首创,但却是由 Apple 推广开来,不禁唏嘘不已。

HEIF 相比 JEPG 能够提高 2 倍的压缩率,而且支持 HEVC 作为其压缩的编解码器;支持透明通道与深度;支持动画(比如动态 GIF , Live Photo);支持图像序列(比如照片的长曝光)。

HEIF 的国际标准是 ISO/IEC 23008-12(June2015)。

HEIF 文件扩展名在不同内容下的差异:

Payload Extension
HEVC .heic
H.264 .avci
any codec .heif

回想一下 iOS 11 的相册支持 GIF 播放了,真的是 Apple 听到大家的呼声了吗?另外 Live Photo 也不再是照片.jpg + 视频.mov 的组合了。

2.1 HEIF 的硬件要求

新系统下所有的设备都能编解码新的图像格式。只不过要 A9 芯片或是酷睿 6 代以后的才支持硬解码。A10 芯片才支持硬编码。不过毕竟是图像,没有视频那么严格的实时性要求。

3. HEVC 与 HEIF 的兼容性

访问 HEIF 图像,可以说在新的操作系统与系统框架层面是全部支持的,用法与原来并没有什么两样,注意格式选择即可。

访问 HEVC 影片资源,同样也是全部支持,甚至连 HLS(HTTP Live Streaming) 协议都已经扩展完毕(毕竟自家的亲生儿子),想必视频站都会尽早跟进,毕竟相同画质能节约 40% 的带宽,省钱就是盈利啊。

传输时,对用户而言是比较无感的:对于生态外,总是会自动转码为 JPEG/H.264 来获得最好的兼容性(比如 Mail, Share Extensions),对于 Apple 自己生态内,会判定目标设备是否能够解码相应内容而选择是否转码(比如: PTP, AirDrop)。

例如:目前测试的情况,使用 iOS 11 beta 2 通过 AirDrop 传输新格式的照片/视频到 macOS 10.12.5 时,因为目标设备不兼容新的格式,iPhone 端会转码后发送 JPEG/H.264 内容;而当我把相同内容传输到 macOS 10.13 beta 2 时,则可以收到 HEIF/HEVC 内容。

4. 使用 HEVC

示例代码中有部分强制解包与 try! ,仅为简化代码,生产环境请严格使用 if let 与 try catch。

4.1 读取 Access

PhotoKit 可以返回 HEVC 资源:

  • // PHImageManager
  • manager.requestPlayerItem(forVideo: asset, options: nil) { (playerItem, dictionary) in
  • // use AVPlayerItem
  • }
  • manager.requestLivePhoto(for: asset, targetSize: size, contentMode: .default, options: nil) { (livePhoto, dictionary) in
  • // use PHLivePhoto
  • }
  • manager.requestExportSession(forVideo: asset, options: nil, exportPreset: preset) { (session, dictionary) in
  • // use AVAssetExportSession
  • }
  • manager.requestAVAsset(forVideo: asset, options: nil) { (asset, audioMix, dictionary) in
  • // use AVAsset
  • }

或者按需要直接返回二进制数据:

  • // PHAssetResourceManager
  • resourceManager.requestData(for: assetResource, options: nil, dataReceivedHandler: { (data) in
  • // use Data
  • }, { (error) in
  • // handle Error
  • })

4.2 播放 Playback

这部分没有破坏性的 API 变化,唯一需要考虑的是不同设备的硬件差异导致的播放能力的不同。

AVAssetTrack 新增了一个 isDecodable 的实例属性,对于某些非实时解码操作会有帮助。

而对于要能够实时播放的内容,调用 isPlayable 查看本机的是否可以播放(毕竟大家都一定有过看幻灯片的经历)。

4.3 拍摄 Capture

4.3.1 拍摄 HEVC 格式的视频

Apple 给出的 Sample Code 是使用手机摄像头拍摄 4K 视频,并使用 AVCaptureMovieFileOutput 写入文件。唯一需要确定的就是本机是否包括 HEVC 的编解码器,并在输出设置中作出相应的选择。

  • let session = AVCaptureSession()
  • session.sessionPreset = .hd4K3840x2160
  • let camera = AVCaptureDevice.default(.builtInWideAngleCamera, for: nil, position: .back)
  • let input = try! AVCaptureDeviceInput(device: camera!)
  • session.addInput(input)
  • let movieFileOutput = AVCaptureMovieFileOutput()
  • session.addOutput(movieFileOutput)
  • session.startRunning()
  • movieFileOutput.startRecording(to: url, recordingDelegate: self)
  • let connection = movieFileOutput.connection(with: .video)
  • if movieFileOutput.availableVideoCodecTypes.contains(.hevc) {
  • outputSetings = [AVVideoCodecKey: AVVideoCodecType.hevc]
  • } else {
  • outputSetings = [AVVideoCodecKey: AVVideoCodecType.h264]
  • }
  • movieFileOutput.setOutputSettings(outputSetings, for: connection!)

4.3.2 使用 HEVC 拍摄 Live Photo

使用 HEVC 拍摄 Live Photo 时也是类似的操作,并且可以享受这些增强功能:视频防抖、音频回放、30 FPS。

  • let photoSettings = AVCapturePhotoSettings()
  • photoSettings.livePhotoMovieFileURL = URL(fileURLWithPath: myFilePath)
  • if photoOutput.availableLivePhotoVideoCodecTypes.contains(.hevc) {
  • photoSettings.livePhotoVideoCodecType = .hevc
  • }
  • photoOutput.capturePhoto(with: photoSettings, delegate: self)

4.3.3 使用 AVAssetWriter 来写入 HEVC 视频文件

同样,指定需要的视频编解码器即可。

  • // AVCaptureVideoDataOutput
  • // iOS 7
  • vdo.recommendedVideoSettingsForAssetWriter(writingTo: .mov)
  • // iOS 11
  • vdo.recommendedVideoSettings(forVideoCodecType: .hevc, assetWriterOutputFileType: .mov)

4.4 输出 Export

4.4.1 使用 AVAssetExportSession 进行转码

当需要从 H.264 转码为 HEVC 格式时,使用新增的 Preset 即可。

  • AVAssetExportPresetHEVC1920x1080
  • AVAssetExportPresetHEVC3840x2160
  • AVAssetExportPresetHEVCHighestQuality

4.4.2 使用 AVAssetWriter 编码

AVAssetWriterInput 的输出设置指定编解码器为 HEVC:

  • settings = [AVVideoCodecKey: AVVideoCodecType.hevc]

当然,你也可以使用 AVOutputSettingsAssistant 来方便的为指定预设得到输出配置:

  • AVOutputSettingsPreset.hevc1920x1080
  • AVOutputSettingsPreset.hevc3840x2160

使用下面这个新增的方法,在输出设置中查询编码器支持的属性

  • let error = VTCopySupportedPropertyDictionaryForEncoder(
  • 3840, 2160,
  • kCMVideoCodecType_HEVC,
  • encoderSpecification,
  • &encoderID, &properties)
  • if error == kVTCouldNotFindVideoEncoderErr {
  • // no HEVC encoder
  • }

Encoder ID 对与一个编码器来说是唯一标识符。Properties 与 encoder ID 可以被用在输出配置中。

4.4.3 使用 VTCompressionSession 来编码采样数据

创建 HEVC 编码器与 H.264 编码器并没有显著区别,指定 Codec 为 kCMVideoCodecType_HEVC 即可。

```swift
let error = VTCompressionSessionCreate(
kCFAllocatorDefault,
3840, 2160,
kCMVideoCodecType_HEVC,
encoderSpecification as CFDictionary,
nil, nil, nil, nil, // using VTCompressionSessionEncodeFrameWithOutputHandler
&session);
if error == kVTCouldNotFindVideoEncoderErr {
// no HEVC encoder

top Created with Sketch.