在使用GPUImage将图片合成到视频中时,遇到这样一个场景:图片需要旋转一定角度后在blend到视频中。
用GPUImageUIElement可以实现这个需求,但是效率很低,因为需要把UIView或者CALayer转成纹理;
比较好的做法是用GPUImageAlphaBlendFilter直接将视频和图片合并到一起。
图片直接合成到视频中是很简单的:
movieInput = GPUImageMovie(url: videoURL)
pictureInput = GPUImagePicture(image: image0)
pictureInput?.processImage()
let blendFilter = GPUImageAlphaBlendFilter()
blendFilter.mix = 1
movieInput?.addTarget(blendFilter)
pictureInput?.addTarget(blendFilter)
blendFilter.addTarget(displayView)
movieInput?.startProcessing()
但是当GPUImagePicture后面接了一个其他的filter之后,就有问题了,理论上的写法应该是这样:
movieInput = GPUImageMovie(url: videoURL)
pictureInput = GPUImagePicture(image: image0)
pictureInput?.processImage()
let transformFilter = GPUImageTransformFilter()
transformFilter.affineTransform = CGAffineTransform(rotationAngle: 0.5)
pictureInput?.addTarget(transformFilter)
let blendFilter = GPUImageAlphaBlendFilter()
blendFilter.mix = 1
movieInput?.addTarget(blendFilter)
transformFilter.addTarget(blendFilter)
blendFilter.addTarget(displayView)
movieInput?.startProcessing()
但实际上这样写会报错,代码跑个几秒钟之后就会挂掉,控制台显示:message from debugger: terminated due to memory issue
查来查去查到了GPUImage的issue上:https://github.com/BradLarson/GPUImage/issues/1551
作者自己也说这是个bug,而且目前他自己还没想出来什么好的解决办法。。。
不过有人给出了一个可行的解决方案:即把filter链改成如下的样子(从下往上看)
GPUImageView
|
|
GPUImageHardLightBlendFilter
| |
| |
| GPUImageGaussianBlurFilter
| |
| |
| GPUImageNormalBlendFilter
| | |
| |(*) |
| | |
GPUImageMovie GPUImagePicture
我试了一下,这个方案确实是可行的,但感觉效率也比较低,因为movie和picture一开始的这一次normalBlend是完全没有必要的,只是为了代码能跑通而已;
要想比较好的解决这个问题,还是得研究GPUImage的源码!
关键就在GPUImageFilter的- (void)renderToTextureWithVertices:(const GLfloat *)vertices textureCoordinates:(const GLfloat *)textureCoordinates;
方法中。
要弄看懂这个方法需要有一点OpenGL的基础,简单来说:
其输入是firstInputFrameBuffer,其输出是outputFrameBuffer,即firstInputFrameBuffer经过这个filter后变成了outputFrameBuffer;
问题的关键也就在这儿,不管filter进行了什么什么操作,或者即使filter什么也没做,firstInputFrameBuffer和outputFrameBuffer也不是同一个对象!
这就是导致GPUImagePicture后面不能接其他filter的原因,
GPUImagePicture初始化的时候,会调用[outputFramebuffer disableReferenceCounting]
,
所以GPUImagePicture的outputFrameBuffer在调用lock或者unlock的时候,都是直接return的,
又因为GPUImagePicture的输入是一张图片,第一次processImage之后,纹理就一直存在了,之后就不用再次处理,也不用通知它后面的filter纹理准备好了(newFrameReadyAt方法),
所以当GPUImagePicture后面接了filter之后,filter的outputFrameBuffer不是GPUImagePicture的outputFrameBuffer,而是重新创建的,它并没有调用[outputFramebuffer disableReferenceCounting]
方法,它lock或者unlock的时候还是有可能会出现framebuffer overrelease error
的问题。
当GPUImagePicture与其他filter进行blend的时候,GPUImagePicture一般作为blendFilter的第二个输入源,而blendFilter大部分是继承自GPUImageTwoInputFilter,
GPUImageTwoInputFilter在- (void)newFrameReadyAtTime:(CMTime)frameTime atIndex:(NSInteger)textureIndex;
方法中会判断是否两个输入源都准备就绪了,如果有就开始绘制,如果没有就继续等着,
如果输入是GPUImagePicture则会跳过这个判断,直接认为输入已经准备好了,直接开始绘制,
所以GPUImagePicture直接与其他视频blend不会有问题,而接了其他filter之后,因为这个filter的outputFrameBuffer不是GPUImagePicture的output,只是个普通的frameBuffer,所以它一直都不会准备好,
所以显示会一直空白,而且绘制完调用unlock的时候,可能会报错。
总的来说,这应该算是GPUImage库的问题,不过也可以用迂回的方式解决,上面提到的方案是可以的,我还试出来另外一种方案:
movieInput = GPUImageMovie(url: videoURL)
pictureInput = GPUImagePicture(image: image0)
pictureInput?.processImage()
let transformFilter = GPUImageTransformFilter()
transformFilter.affineTransform = CGAffineTransform(rotationAngle: 0.5)
pictureInput?.addTarget(transformFilter)
let progressFilter = GPUImageFilter()
movieInput?.addTarget(progressFilter)
progressFilter.frameProcessingCompletionBlock = { [unowned self] input, time in
self.pictureInput?.processImage()
}
let blendFilter = GPUImageAlphaBlendFilter()
blendFilter.mix = 1
progressFilter.addTarget(blendFilter)
transformFilter.addTarget(blendFilter)
blendFilter.addTarget(displayView)
movieInput?.startProcessing()
即在movieInput的后面加一个filter,这个filter什么都不用做,只需要在这个filter的frameProcessingCompletionBlock回调中冲洗调用GPUImagePicture的processImage方法即可。
注意这里GPUImageMovie虽然也有frameProcessingCompletionBlock这个属性,但是其内部并没有调用,所以才需要一个progressFilter,继承GPUImageMovie实现frameProcessingCompletionBlock这个回调应该也是可以的。