iOS 11 HEVC 快速预览

前天猜想中准确预测了 H.265(HEVC) 的到来,然后又在小伙伴讨论中压中了 HomePod 的价格,感觉耗尽了洪荒之力。于是昨天几乎是懒散了一天,毕竟年纪大了,熬不起夜了。不过不得不说 Apple 的审核效率有了很大的提高,昨天下午给客户打的修复包早上已经审核通过了,简直云霄飞车🎢!下面言归正传。

早上看到微博,似乎目前 iOS 设备无法支持 HEVC 硬件解码。具体涉及到的是下面这个 iOS 11 上新增的函数:

1
2
@available(iOS 11.0, *)
public func VTIsHardwareDecodeSupported(_ codecType: CMVideoCodecType) -> Bool

于是心急火燎的测试一下,果然是大大的 flase 🤦‍♂!难道苹果大爷又开倒车了?不过实际上依照我之后的测试情况来猜测,应该仅仅是个 Bug 。

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

下面从 HEVC 编码器开始,实际上和 H.264(AVC) 时期的代码并没有太多的改动,需要注意的是 profileLevel 要使用 HEVC 对应的值,当然从这两项也可以看出,Apple 这次只支持到 HEVC profile 的 Version 1

1
2
3
4
@available(iOS 11.0, *)
public let kVTProfileLevel_HEVC_Main_AutoLevel: CFString
@available(iOS 11.0, *)
public let kVTProfileLevel_HEVC_Main10_AutoLevel: CFString

然后把摄像头采集到的数据丢进去就行了。

这里遇到了一个坑,AVFoundation 在 Swift 4 中,变的更加 Swifty Style 了,于是很多函数签名发生了变化,特别注意 NSObjectProtocol 中那些 optional func 。比如下面这个,盲目的复制代码导致了我半天收不到相机的输出😂。

1
2
3
4
5
6
7
8
9
10
11
extension VideoCapture: AVCaptureVideoDataOutputSampleBufferDelegate {
// Swift 4
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
self.outputHandler?(sampleBuffer)
}

// Swift 3
func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputSampleBuffer sampleBuffer: CMSampleBuffer!, from connection: AVCaptureConnection!) {
self.outputHandler?(sampleBuffer)
}
}

然后看编码器的输出,直接打印一下 buffer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
CMSampleBuffer 0x10430c690 retainCount: 9 allocator: 0x1b3c41dc0
invalid = NO
dataReady = YES
makeDataReadyCallback = 0x0
makeDataReadyRefcon = 0x0
formatDescription = <CMVideoFormatDescription 0x1c4452c60 [0x1b3c41dc0]> {
mediaType:'vide'
mediaSubType:'hvc1'
mediaSpecific: {
codecType: 'hvc1' dimensions: 1920 x 1080
}
extensions: {<CFBasicHash 0x1c4469cc0 [0x1b3c41dc0]>{type = immutable dict, count = 2,
entries =>
0 : <CFString 0x1acc8c8b8 [0x1b3c41dc0]>{contents = "SampleDescriptionExtensionAtoms"} = <CFBasicHash 0x1c4276d80 [0x1b3c41dc0]>{type = immutable dict, count = 1,
entries =>
0 : <CFString 0x1acc94d58 [0x1b3c41dc0]>{contents = "hvcC"} = <CFData 0x104321cd0 [0x1b3c41dc0]>{length = 563, capacity = 563, bytes = 0x010160000000b0000000000078f000fc ... 15e12e0930610789}
}

2 : <CFString 0x1acc8c918 [0x1b3c41dc0]>{contents = "FormatName"} = HEVC
}
}
}
sbufToTrackReadiness = 0x0
numSamples = 1
sampleTimingArray[1] = {
{PTS = {40888716474703/1000000000 = 40888.716}, DTS = {INVALID}, duration = {INVALID}},
}
sampleSizeArray[1] = {
sampleSize = 154297,
}
sampleAttachmentsArray[1] = {
sample 0:
DependsOnOthers = false
}
dataBuffer = 0x1c41069c0

重点关注 CMVideoFormatDescription,其中 codecType: 'hvc1' 确认已经是 HEVC 编码成功。

原来使用 H.264(AVC) 时候,提取 SPSPPS 是使用 avcC 字段。而在 HEVC 中明显是用 hvcC 字段。由 ParameterSets 还原为 CMVideoFormatDescription 也增加了对应的函数:

1
2
@available(iOS 11.0, *)
public func CMVideoFormatDescriptionCreateFromHEVCParameterSets(_ allocator: CFAllocator?, _ parameterSetCount: Int, _ parameterSetPointers: UnsafePointer<UnsafePointer<UInt8>>, _ parameterSetSizes: UnsafePointer<Int>, _ NALUnitHeaderLength: Int32, _ formatDescriptionOut: UnsafeMutablePointer<CMFormatDescription?>) -> OSStatus

至于如何解包 hvcC boxSPSPPS 呢?暂时只找到了 ISO 标准,这里参看这里,或者这个 C++ 的实现 stackoverflow,不过幸好只是体力活了,容我再测试消化一下。

而别的内容,什么关键帧,帧数据的都和 H.264 时代一样处理即可。

实际的运行时我的 Demo 消耗大约是:CPU 不超过 15%,内存 13 MB。照这个情况看,应该是硬解完成的,和 H.264 的情况差不多。

应该接下来的 Session 中还会有更多具体的内容,就先这样了。