本文档描述使用AVAssetWriterInputPixelBufferAdaptor简化Metal渲染结果从BGRA到yuv420p的转换,适用于写成MP4或MOV容器。以前有人用类似的办法暴力实现iOS 8以下的H.264硬解,代价是消耗用户的闪存写次数。
文档结构:
- 初始化AVAssetWriterInput
- 初始化AVAssetWriterInputPixelBufferAdaptor
- CVPixelBuffer写入AVAssetWriterInputPixelBufferAdaptor
- 讨论:指定BT. 709 YUV转RGB矩阵
Metal Camera开发4:渲染到CVPixelBuffer实现了CVPixelBuffer存储渲染结果,对于摄像头开发,通常希望将渲染结果编码成H.264或H.265(iOS 11支持),iOS平台的H.264编码器原生支持yuv420sp VideoRange及FullRange,而Metal渲染到CVPixelBuffer时像素格式为BGRA,显然需要进行像素格式转换,比如使用libyuv、Metal 着色器实现RGBA转yuv或GLSL实现。对于将数据写到本地的需求,AVFoundation提供了AVAssetWriterInputPixelBufferAdaptor简化RGB转yuv的步骤。GPUImage也使用了这种方式实现OpenGL ES的渲染结果编码成H.264。下面描述它的编程过程。
AVAssetWriterInputPixelBufferAdaptor必须配合AVAssetWriterInput使用,不然没法调用构造函数(手动斜眼.jpg)。AVAssetWriterInputPixelBufferAdaptor类的头文件描述了它的功能:
Pixel buffers not in a natively supported format will be converted internally prior to encoding when possible.
对于编码Metal的渲染结果,初始化AVAssetWriterInputPixelBufferAdaptor指定sourcePixelBufferAttributes使用kCVPixelBufferPixelFormatTypeKey为kCVPixelFormatType_32BGRA,之后的像素格式转换都由AVFoundation内部实现,省去了手动实现BGRA转yuv。当编码CoreImage的渲染结果,kCVPixelBufferPixelFormatTypeKey对应的值为kCVPixelFormatType_32ARGB。总之,需要和数据源对应起来,否则视频会表现出异常颜色。接下来逐一描述编程步骤。
1. 初始化AVAssetWriterInput
let videoCompressionProperties = [
AVVideoAverageBitRateKey: 1080 * 1920 * 15.15
]
let videoSettings: [String : Any] = [
AVVideoCodecKey: AVVideoCodecH264,
AVVideoWidthKey: 1080,
AVVideoHeightKey: 1920,
AVVideoCompressionPropertiesKey: videoCompressionProperties
]
self.videoWriterInput = AVAssetWriterInput(mediaType: AVMediaTypeVideo, outputSettings: videoSettings)
AVAssetWriterInput还需添加到AVAssetWriter,网上参考代码非常丰富,在此不一一描述。
2. 初始化AVAssetWriterInputPixelBufferAdaptor
var videoWriterInputPixelBufferAdaptor: AVAssetWriterInputPixelBufferAdaptor?
//-------
let sourcePixelBufferAttributes = [
kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA,
kCVPixelBufferWidthKey as String: 1080,
kCVPixelBufferHeightKey as String: 1920
]
self.videoWriterInputPixelBufferAdaptor = AVAssetWriterInputPixelBufferAdaptor(
assetWriterInput: self.videoWriterInput!,
sourcePixelBufferAttributes: sourcePixelBufferAttributes
)
AVAssetWriterInputPixelBufferAdaptor是否应该在AVAssetWriterInput添加到AVAssetWriter前初始化?经实验,这一操作的顺序不影响程序的正常运行,在AVAssetWriter.startWriting()前初始化即可。
3. CVPixelBuffer写入AVAssetWriterInputPixelBufferAdaptor
self.videoWriterInputPixelBufferAdaptor!.assetWriterInput.isReadyForMoreMediaData {
let whetherPixelBufferAppendedtoAdaptor = self.videoWriterInputPixelBufferAdaptor!.append(renderPixelBuffer!, withPresentationTime: presentationTime)
if whetherPixelBufferAppendedtoAdaptor {
print("adaptor appended CVPixelBuffer successfully")
} else {
print("adaptor appended CVPixelBuffer failed")
}
}
renderPixelBuffer为Metal渲染的目标CVPixelBuffer,即它存储了最终的渲染结果,细节可参考Metal Camera开发4:渲染到CVPixelBuffer。
4. 讨论:指定BT. 709 YUV转RGB矩阵
GPUImageMovieWriter类的createDataFBO方法有这么一段注释:
/* AVAssetWriter will use BT.601 conversion matrix for RGB to YCbCr conversion
* regardless of the kCVImageBufferYCbCrMatrixKey value.
* Tagging the resulting video file as BT.601, is the best option right now.
* Creating a proper BT.709 video is not possible at the moment.
*/
在iOS 10的测试过程中,发现已支持kCVImageBufferYCbCrMatrixKey设置为kCVImageBufferTransferFunction_ITU_R_709_2。参考代码如下。
CVBufferSetAttachment(pixelBuffer!, kCVImageBufferColorPrimariesKey, kCVImageBufferColorPrimaries_ITU_R_709_2, CVAttachmentMode.shouldPropagate)
CVBufferSetAttachment(pixelBuffer!, kCVImageBufferYCbCrMatrixKey, kCVImageBufferTransferFunction_ITU_R_709_2, CVAttachmentMode.shouldPropagate)
CVBufferSetAttachment(pixelBuffer!, kCVImageBufferTransferFunctionKey, kCVImageBufferTransferFunction_ITU_R_709_2, CVAttachmentMode.shouldPropagate)
FFmpeg读取生成的视频信息如下所示。