在使用GPUImage將圖片合成到視頻中時,遇到這樣一個場景:圖片需要旋轉(zhuǎn)一定角度后在blend到視頻中。
用GPUImageUIElement可以實現(xiàn)這個需求,但是效率很低,因為需要把UIView或者CALayer轉(zhuǎn)成紋理;
比較好的做法是用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的源碼!
關(guān)鍵就在GPUImageFilter的- (void)renderToTextureWithVertices:(const GLfloat *)vertices textureCoordinates:(const GLfloat *)textureCoordinates;方法中。
要弄看懂這個方法需要有一點OpenGL的基礎,簡單來說:
其輸入是firstInputFrameBuffer,其輸出是outputFrameBuffer,即firstInputFrameBuffer經(jīng)過這個filter后變成了outputFrameBuffer;
問題的關(guān)鍵也就在這兒,不管filter進行了什么什么操作,或者即使filter什么也沒做,firstInputFrameBuffer和outputFrameBuffer也不是同一個對象!
這就是導致GPUImagePicture后面不能接其他filter的原因,
GPUImagePicture初始化的時候,會調(diào)用[outputFramebuffer disableReferenceCounting],
所以GPUImagePicture的outputFrameBuffer在調(diào)用lock或者unlock的時候,都是直接return的,
又因為GPUImagePicture的輸入是一張圖片,第一次processImage之后,紋理就一直存在了,之后就不用再次處理,也不用通知它后面的filter紋理準備好了(newFrameReadyAt方法),
所以當GPUImagePicture后面接了filter之后,filter的outputFrameBuffer不是GPUImagePicture的outputFrameBuffer,而是重新創(chuàng)建的,它并沒有調(diào)用[outputFramebuffer disableReferenceCounting]方法,它lock或者unlock的時候還是有可能會出現(xiàn)framebuffer overrelease error的問題。
當GPUImagePicture與其他filter進行blend的時候,GPUImagePicture一般作為blendFilter的第二個輸入源,而blendFilter大部分是繼承自GPUImageTwoInputFilter,
GPUImageTwoInputFilter在- (void)newFrameReadyAtTime:(CMTime)frameTime atIndex:(NSInteger)textureIndex;方法中會判斷是否兩個輸入源都準備就緒了,如果有就開始繪制,如果沒有就繼續(xù)等著,
如果輸入是GPUImagePicture則會跳過這個判斷,直接認為輸入已經(jīng)準備好了,直接開始繪制,
所以GPUImagePicture直接與其他視頻blend不會有問題,而接了其他filter之后,因為這個filter的outputFrameBuffer不是GPUImagePicture的output,只是個普通的frameBuffer,所以它一直都不會準備好,
所以顯示會一直空白,而且繪制完調(diào)用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回調(diào)中沖洗調(diào)用GPUImagePicture的processImage方法即可。
注意這里GPUImageMovie雖然也有frameProcessingCompletionBlock這個屬性,但是其內(nèi)部并沒有調(diào)用,所以才需要一個progressFilter,繼承GPUImageMovie實現(xiàn)frameProcessingCompletionBlock這個回調(diào)應該也是可以的。