1. 程式人生 > >GPUImage整體框架概述

GPUImage整體框架概述

GPUImage是基於GUP影象和視訊處理的iOS開源框架,它採用鏈式傳遞每一層渲染的幀快取物件,通過addTarget:方法為鏈條新增每層的filter,直到最後通過GPUImageView來顯示。

這裡通過研究GPUImage是如何將攝像頭抓取的影象經過一層一層濾鏡渲染最後展示給使用者,來學習GPUImage的整體框架設計原理。

首先介紹GPUImage的幾個主要的類

GLProgram //載入頂點著色器和片元著色器程式並進行編譯連結最終使用,著色器中attribute新增等

GPUImageOutput //抽象類,實現addTarget:以及從當前幀快取獲取影象等介面

GPUImageFilter //繼承自GPUImageOutput,所有濾鏡的父類(除去Group濾鏡),主要提供給著色器傳遞引數的介面,以及渲染當前的幀快取並傳遞給下一層target,鏈式結構的實現主要就是在newFrameReadyAtTime: atIndex:方法中,下面會詳細講述

GPUImageFramebuffer //OpenGL的FBO就是通過它實現的

GPUImageFramebufferCache //實現幀快取的重用機制

GPUImageTwoInputFilter //所有多層紋理特效都是通過它來實現的,比如抖音的“幻覺”特效

GPUImageFilterGroup //組合濾鏡

GPUImageVideoCamera //實現攝像頭的實時視訊和音訊的採集

GPUImagePicture //可以作為混合濾鏡的第N個紋理,比如抖音中那些萌萌的表情就可以通過它來實現

GPUImageView //對渲染好的影象進行顯示

GPUImageMovieWriter //儲存視訊

下面開始跑一遍流程

1. 建立GPUImageVideoCamera物件並設定代理,然後通過startCameraCapture開啟抓屏

2. 我們抓取到的每一幀視訊和音訊資料都會通過以下回調返回給我們

- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection

這裡我們不討論對音訊的處理,這個方法對抓取到的視訊幀,主要實現了兩步處理

if (self.delegate)
{
    [self.delegate willOutputSampleBuffer:sampleBuffer];
}
            
[self processVideoSampleBuffer:sampleBuffer];

首先告訴我們的代理,我將要輸出一幀影象啦,這裡的幀資料sampleBuffer是沒有經過任何濾鏡特效處理的,因此,如果你在這個代理方法中可以對後續的濾鏡進行一些使用者切換或者調整什麼的操作。

然後我們具體來看這個processVideoSampleBuffer:的實現

3. processVideoSampleBuffer:的實現

這裡有一個判斷

if ([GPUImageContext supportsFastTextureUpload] && captureAsYUV) //如果支援紋理快取並且採用YUV視訊編碼

由於前面我們已經配置了kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange,因此這裡只討論這種情況,繼續看if語句中的實現。由於YUV視訊幀分為亮度和色度兩個紋理,分別用GL_LUMINANCE格式和GL_LUMINANCE_ALPHA格式讀取。

通過CVOpenGLESTextureCacheCreateTextureFromImage方法將幀資訊載入到[[GPUImageContext sharedImageProcessingContext] coreVideoTextureCache]快取中,然後通過convertYUVToRGBOutput來進行編碼轉換,在這個方法裡,我們將影象渲染到了outputFramebuffer幀快取中,最後我們呼叫

 [self updateTargetsForVideoCameraUsingCacheTextureAtWidth:rotatedImageBufferWidth height:rotatedImageBufferHeight time:currentTime];

4. - (void)updateTargetsForVideoCameraUsingCacheTextureAtWidth:(int)bufferWidth height:(int)bufferHeight time:(CMTime)currentTime 的實現

這個方法裡面對當前物件的每個target會首先呼叫

[currentTarget setInputFramebuffer:outputFramebuffer atIndex:textureIndexOfTarget];

- (void)setInputFramebuffer:(GPUImageFramebuffer *)newInputFramebuffer atIndex:(NSInteger)textureIndex;
{
    firstInputFramebuffer = newInputFramebuffer;
    [firstInputFramebuffer lock];
}

firstInputFramebuffer對應的是著色器中的第一個取樣器即inputImageTexture,如果你用到混合紋理的話,secondInputFramebuffer對應的就是inputImageTexture2

然後呼叫

[currentTarget newFrameReadyAtTime:currentTime atIndex:textureIndexOfTarget];

5. newFrameReadyAtTime:atIndex:的實現

首先呼叫

 [self renderToTextureWithVertices:imageVertices textureCoordinates:[[self class] textureCoordinatesForRotation:inputRotation]];

這裡主要是將當前濾鏡的特效渲染到outputFramebuffer快取幀

然後呼叫

 [self informTargetsAboutNewFrameAtTime:frameTime];

6. informTargetsAboutNewFrameAtTime:的實現

首先通過呼叫

[self setInputFramebufferForTarget:currentTarget atIndex:textureIndex];

- (void)setInputFramebufferForTarget:(id<GPUImageInput>)target atIndex:(NSInteger)inputTextureIndex;
{
    [target setInputFramebuffer:[self framebufferForOutput] atIndex:inputTextureIndex];
}

來將outputFramebuffer幀快取的內容賦值給firstInputFramebuffer,注意這裡是針對只有一個取樣器的時候

然後對當前濾鏡的所有target呼叫

[currentTarget newFrameReadyAtTime:frameTime atIndex:textureIndex];

這個方法上面提到過,這樣就形成了一個鏈式響應結構,就這樣一層一層執行到最後那個target也就是GPUImageView把影象顯示出來。

參考資料