教程
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是我最大的源動力,你的意見能讓我走得更遠。