標籤:

深入理解 CVPixelBufferRef

在iOS里,我們經常能看到 CVPixelBufferRef 這個類型,在Camera 採集返回的數據里得到一個CMSampleBufferRef,而每個CMSampleBufferRef里則包含一個 CVPixelBufferRef,在視頻硬解碼的返回數據里也是一個 CVPixelBufferRef。

顧名思義,CVPixelBufferRef 是一種像素圖片類型,由於CV開頭,所以它是屬於 CoreVideo 模塊的。

iOS喜歡在對象命名前面用縮寫表示它屬於的模塊,比如 CF 代表 CoreFoundation,CG代表 CoreGraphic,CM代表 CoreMedia。既然屬於 CoreVideo那麼它就和視頻處理相關了。

它是一個C對象,而不是Objective C對象,所以它不是一個類,而是一個類似Handle的東西。從代碼頭文件的定義來看

CVPixelBufferRef 就是用 CVBufferRef typedef而來的,而CVBufferRef 本質上就是一個void *,至於這個void *具體指向什麼數據只有系統才知道了。

所以我們看到 所有對 CVPixelBufferRef 進行操作的函數都是純 C 函數,這很符合iOS CoreXXXX系列 API 的風格。

比如 CVPixelBufferGetWidth, CVPixelBufferGetBytesPerRow

通過API可以看出來,CVPixelBufferRef里包含很多圖片相關屬性,比較重要的有 width,height,PixelFormatType等。

由於可以有不同的PixelFormatType,說明他可以支持多種點陣圖格式,除了常見的 RGB32以外,還可以支持比如 kCVPixelFormatType_420YpCbCr8BiPlanarFullRange,這種YUV多平面的數據格式,這個類型里 BiPlanar 表示雙平面,說明它是一個 NV12的YUV,包含一個Y平面和一個UV平面。通過CVPixelBufferGetBaseAddressOfPlane可以得到每個平面的數據指針。在得到 Address之前需要調用CVPixelBufferLockBaseAddress,這說明CVPixelBufferRef的內部存儲不僅是內存也可能是其它外部存儲,比如現存,所以在訪問前要lock下來實現地址映射,同時lock也保證了沒有讀寫衝突。

由於是C對象,它是不受 ARC 管理的,就是說要開發者自己來管理引用計數,控制對象的生命周期,可以用CVPixelBufferRetain,CVPixelBufferRelease函數用來加減引用計數,其實和CFRetain和CFRelease是等效的,所以可以用 CFGetRetainCount來查看當前引用計數。

如果要顯示 CVPixelBufferRef 里的內容,通常有以下幾個思路。

把 CVPixelBufferRef 轉換成 UIImage,就可以直接賦值給UIImageView的image屬性,顯示在UIImageView上,示例代碼

+ (UIImage*)uiImageFromPixelBuffer:(CVPixelBufferRef)p {n CIImage* ciImage = [CIImage imageWithCVPixelBuffer:p];n n CIContext* context = [CIContext contextWithOptions:@{kCIContextUseSoftwareRenderer : @(YES)}];n n CGRect rect = CGRectMake(0, 0, CVPixelBufferGetWidth(p), CVPixelBufferGetHeight(p));n CGImageRef videoImage = [context createCGImage:ciImage fromRect:rect];n n UIImage* image = [UIImage imageWithCGImage:videoImage];n CGImageRelease(videoImage);n n return image;n}n

從代碼可以看出來,這個轉換有點複雜,中間經歷了多個步驟,所以性能是很差的,只適合偶爾轉換一張圖片,用於調試截圖等,用於顯示視頻肯定是不行的。

另一個思路是用OpenGL來渲染,CVPixelBufferRef是可以轉換成一個 openGL texture的,方法如下:

CVOpenGLESTextureRef pixelBufferTexture; CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault,n _textureCache,n pixelBuffer,n NULL,n GL_TEXTURE_2D,n GL_RGBA,n width,n height,n GL_BGRA,n GL_UNSIGNED_BYTE,n 0,n &pixelBufferTexture);n

其中,_textureCache 代表一個 Texture緩存,每次生產的Texture都是從緩存獲取的,這樣可以省掉反覆創建Texture的開銷,_textureCache要實現創建好,創建方法如下

CVOpenGLESTextureCacheCreate(kCFAllocatorDefault, NULL, _context, NULL, &_textureCache);n

其中 _context 是 openGL的 context,在iOS里就是 EAGLContext *

pixelBufferTexture還不是openGL的Texture,調用CVOpenGLESTextureGetName才能獲得在openGL可以使用的Texture ID。

當獲得了 Texture ID後就可以用openGL來繪製了,這裡推薦用 GLKView 來做繪製

glUseProgram(_shaderProgram);n n glActiveTexture(GL_TEXTURE0);n glBindTexture(GL_TEXTURE_2D, textureId);n glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);n glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);n glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);n glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);n n glDrawArrays(GL_TRIANGLE_FAN, 0, 4);n

當然這不是全部代碼,完整的繪製openGL代碼還有很多,openGL是著名的啰嗦冗長,還有openGL Context創建 shader編譯 DataBuffer載入等。

本質上這段代碼是為了把 Texture的內容繪製到 openGL的frame buffer里,然後再把frame buffer貼到 CAEAGLayer。

這個從CVPixelBufferRef獲取的texture,和原來的CVPixelBufferRef 對象共享同一個存儲,就是說如果改變了Texture的內容,那麼CVPixelBufferRef的內容也會改變。利用這一點我們就可可以用openGL的繪製方法向CVPixelBufferRef對象輸出內容了。比如可以給CVPixelBufferRef的內容加圖形特效打水印等。

除了從系統API里獲得CVPixelBufferRef外,我們也可以自己創建CVPixelBufferRef

+(CVPixelBufferRef)createPixelBufferWithSize:(CGSize)size {n const void *keys[] = {n kCVPixelBufferOpenGLESCompatibilityKey,n kCVPixelBufferIOSurfacePropertiesKey,n };n const void *values[] = {n (__bridge const void *)([NSNumber numberWithBool:YES]),n (__bridge const void *)([NSDictionary dictionary])n };n n OSType bufferPixelFormat = kCVPixelFormatType_32BGRA;n n CFDictionaryRef optionsDictionary = CFDictionaryCreate(NULL, keys, values, 2, NULL, NULL);n n CVPixelBufferRef pixelBuffer = NULL;n CVPixelBufferCreate(kCFAllocatorDefault,n size.width,n size.height,n bufferPixelFormat,n optionsDictionary,n &pixelBuffer);n n CFRelease(optionsDictionary);n n return pixelBuffer;n}n

創建一個 BGRA 格式的PixelBuffer,注意kCVPixelBufferOpenGLESCompatibilityKey和kCVPixelBufferIOSurfacePropertiesKey這兩個屬性,這是為了實現和openGL兼容,另外有些地方要求CVPixelBufferRef必須是 IO Surface。

CVPixelBufferRef是iOS視頻採集處理編碼流程的重要中間數據媒介和紐帶,理解CVPixelBufferRef有助於寫出高性能可靠的視頻處理。

要進一步理解CVPixelBufferRef還需要學習 YUV,color range,openGL等知識。


推薦閱讀:

WebGL 和 OpenGL ES 有什麼區別?
這款遊戲中 主角被遮擋部分變成半透明的效果是如何實現的?
Unity-Shader和OpenGL-Shader有什麼不同之處?
如何評價CSDN學院姜雪偉的《3D遊戲引擎高端實戰培訓》課程?
操作系統是如何管理GPU等計算資源的?

TAG:iOS | 视频 | OpenGL |