OpenGL ES實踐教程(二)攝像頭采集數據和渲染

教程

OpenGLES入門教程1-Tutorial01-GLKit
OpenGLES入門教程2-Tutorial02-shader入門
OpenGLES入門教程3-Tutorial03-三維變換
OpenGLES入門教程4-Tutorial04-GLKit進階
OpenGLES進階教程1-Tutorial05-地球月亮
OpenGLES進階教程2-Tutorial06-光線
OpenGLES進階教程3-Tutorial07-粒子效果
OpenGLES進階教程4-Tutorial08-幀緩存
OpenGLES進階教程5-Tutorial09-碰碰車
OpenGLES進階教程6-Tutorial10-平截體優化
OpenGLES進階教程7-Tutorial11-天空盒效果
OpenGLES進階教程8-Tutorial12-obj文件和mtl文件解析
OpenGLES實踐教程1-Demo01-AVPlayer
這一篇教程是攝像頭采集數據和渲染,包括了三部分內容,渲染部分-OpenGL ES,攝像頭采集圖像部分-AVFoundation和圖像數據創建紋理部分-GPUImage

核心思路

1、攝像頭采集

AVFoundation的常用類介紹:
AVCaptureDevice 輸入設備,包括攝像頭、麥克風。
AVCaptureInput 輸入數據源
AVCaptureOutput 輸出數據源
AVCaptureSession 會話,協調輸入與輸出之間的數據流
AVCaptureVideoPreviewLayer 預覽效果的layer



采集流程:

  • 1、新建會話,設置圖像大小;創建處理隊列;
    self.mCaptureSession = [[AVCaptureSession alloc] init];
    self.mCaptureSession.sessionPreset = AVCaptureSessionPreset640x480;
    mProcessQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
  • 2、新建攝像頭輸入,設置為前置攝像頭;創建設備輸入,并添加到會話;
AVCaptureDevice *inputCamera = nil;
    NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
    for (AVCaptureDevice *device in devices)
    {
        if ([device position] == AVCaptureDevicePositionFront)
        {
            inputCamera = device;
        }
    }
    self.mCaptureDeviceInput = [[AVCaptureDeviceInput alloc] initWithDevice:inputCamera error:nil];
    if ([self.mCaptureSession canAddInput:self.mCaptureDeviceInput]) {
        [self.mCaptureSession addInput:self.mCaptureDeviceInput];
    }
  • 3、創建數據輸出,設置delegate和輸出格式,添加到會話;
    self.mCaptureDeviceOutput = [[AVCaptureVideoDataOutput alloc] init];
    [self.mCaptureDeviceOutput setAlwaysDiscardsLateVideoFrames:NO];
    self.mGLView.isFullYUVRange = NO;
    [self.mCaptureDeviceOutput setVideoSettings:[NSDictionary dictionaryWithObject:[NSNumber numberWithInt:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange] forKey:(id)kCVPixelBufferPixelFormatTypeKey]];
    [self.mCaptureDeviceOutput setSampleBufferDelegate:self queue:mProcessQueue];
    if ([self.mCaptureSession canAddOutput:self.mCaptureDeviceOutput]) {
        [self.mCaptureSession addOutput:self.mCaptureDeviceOutput];
    }
    AVCaptureConnection *connection = [self.mCaptureDeviceOutput connectionWithMediaType:AVMediaTypeVideo];
    [connection setVideoOrientation:AVCaptureVideoOrientationPortraitUpsideDown];

思考1:這里的AVCaptureConnection有何作用?

  • 4、開始錄制;在delegate中接受圖像幀數據;
  • 開始會話
    [self.mCaptureSession startRunning];
  • 處理圖像幀;
    CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
    [self.mGLView displayPixelBuffer:pixelBuffer];

2、圖像紋理數據創建

這一部分的代碼參考自GPUImage的GPUImageVideoCamera類,YUV視頻幀分為亮度和色度兩個紋理,分別用GL_LUMINANCE格式和GL_LUMINANCE_ALPHA格式讀取。

  • 1、創建亮度紋理;
        glActiveTexture(GL_TEXTURE0);
        err = CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault,
                                                           _videoTextureCache,
                                                           pixelBuffer,
                                                           NULL,
                                                           GL_TEXTURE_2D,
                                                           GL_LUMINANCE,
                                                           frameWidth,
                                                           frameHeight,
                                                           GL_LUMINANCE,
                                                           GL_UNSIGNED_BYTE,
                                                           0,
                                                           &_lumaTexture);
  • 2、配置亮度紋理屬性
        glBindTexture(CVOpenGLESTextureGetTarget(_lumaTexture), CVOpenGLESTextureGetName(_lumaTexture));
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
  • 3、創建色度紋理;

        glActiveTexture(GL_TEXTURE1);
        err = CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault,
                                                           _videoTextureCache,
                                                           pixelBuffer,
                                                           NULL,
                                                           GL_TEXTURE_2D,
                                                           GL_LUMINANCE_ALPHA,
                                                           frameWidth / 2,
                                                           frameHeight / 2,
                                                           GL_LUMINANCE_ALPHA,
                                                           GL_UNSIGNED_BYTE,
                                                           1,
                                                           &_chromaTexture);
  • 4、配置色度紋理;
        glBindTexture(CVOpenGLESTextureGetTarget(_chromaTexture), CVOpenGLESTextureGetName(_chromaTexture));
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

思考2:這里為何輸出的是YUV幀?如何配置輸出的視頻幀格式?

OpenGL ES渲染

OpenGL ES的渲染流程在前文多有介紹,這里不再贅述。講講自己遇到的問題。

  • 1、黑屏
    黑屏的現象出現多次,首先是
    CVOpenGLESTextureCacheCreateTextureFromImage failed (error: -6683)
    紋理創建失敗導致的黑屏,在正確配置好顏色格式,解決;
    解決所有報錯后,仍常黑屏;
    檢查紋理代碼,正常;
    檢查顏色緩沖區代碼,正常;
    檢查頂點坐標代碼,正常;
    檢查紋理坐標代碼,正常;
    采用最后的手段,capture GPU Frame,查看GPU的狀態信息。
    發現,present的顏色緩沖區無效;驚訝之余,添加下面的代碼,斷點。
if ([EAGLContext currentContext] == _context) {
        [_context presentRenderbuffer:GL_RENDERBUFFER];
    }

竟然沒有調用,發現問題所在。
添加以下代碼之后,問題解決。
[EAGLContext setCurrentContext:_context];
疑惑:為何之前調用過一次設置context之后,會需要再次調用context?代碼其他地方并無設置context 的地方。
解疑:因為處于新的線程!!!

  • 2、顏色不對
    demo實現過程中遇到顏色不對的情況,圖像的亮度沒有問題,色度出現偏差,效果如下:

    檢查了創建紋理的過程,沒有發現錯誤;
    修改顏色空間,會導致顏色更加異常;
    檢查是否頂點著色器的偏移有誤差,沒有問題;
    最后發現圖片偏綠,在頂點著色器找到問題代碼:
    yuv.yz = (texture2D(SamplerUV, texCoordVarying).rg - vec2(0.5, 0.5));
    正確的取值應該是ra,我寫成了rg,導致圖像偏綠。

總結

旋轉圖像的的數據是個耗性能的操作,如果是用AVAssetWriter寫QuickTime movie文件,更好的做法是設置AVAssetWriterInput的transform屬性,而不是修改AVCaptureVideoDataOutput,真正的去修改圖像數據。
光看教程是學不會OpenGL ES的,下載教程自己改改代碼,自己感興趣的想法就去實現它。
還有就是,遇到問題多嘗試,多查資料。如果絕望,那么就洗洗睡,明天說不定就解決了。
你也喜歡這種腦袋一直有問題在思考的感覺嗎?

思考題

思考1:AVCaptureConnection可以使錄制出來的圖像上下顛倒;
參考GPUImage 的注釋:
From the iOS 5.0 release notes:
In previous iOS versions, the front-facing camera would always deliver buffers in VCaptureVideoOrientationLandscapeLeft
and the back-facing camera would always deliver buffers in
AVCaptureVideoOrientationLandscapeRight.

思考2:在前面的kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange表明了輸出的顏色格式為YUV視頻幀,并且顏色空間為(luma=[16,235] chroma=[16,240])。
iOS通常支持三種格式:
kCVPixelFormatType_420YpCbCr8BiPlanarFullRange
kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange
kCVPixelFormatType_32BGRA
如果遇到了Failed to create IOSurface image (texture)
CVOpenGLESTextureCacheCreateTextureFromImage failed (error: -6683)
這兩個錯誤,一般是配置的顏色輸出格與
CVOpenGLESTextureCacheCreateTextureFromImage的參數不對應;

代碼地址 - 你的star和fork是我最大的源動力,你的意見能讓我走得更遠。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容