解決GPUImagePicture接一個filter再與其他視頻合并時會報錯的問題(message from debugger: terminated due to memory issue 或...

在使用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)應該也是可以的。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容