这次来研究下GPUImage采集视频滤镜的处理。
使用滤镜渲染采集视频
1 |
|
这里使用GPUImageVideoCamera来作为输入源,然后添加一个滤镜,再添加一个GPUImageView输出显示。
使用GPUImageVideoCamera采集视频数据
初始化方法
1 |
|
首先创建了AVCaptureSession以及相关的输入输出。
1 |
|
然后设置采集输出的数据类型,默认是kCVPixelFormatType_420YpCbCr8BiPlanarFullRange的。
1 |
|
接下来判断是否采集NV12数据,如果采集的话,会创建一个OpenGL的program,用于后续将采集到的NV12数据转换为RGBA。
1 |
|
最后则是添加为videoDataOutput的代理,用于接受和处理采集的视频数据。
采集数据后的操作
接下来看在videoDataOutput的代理方法中的代码。
1 |
|
可以看到主要就是判断sampleBuffer是音频帧还是视频帧,然后执行了一个代理方法用于给使用者获取到滤镜处理之前的数据。之后就是将视频帧作为参数执行processVideoSampleBuffer:方法。
processVideoSampleBuffer方法
1 |
|
首先是获取一些视频帧的信息,最主要的是确定颜色格式,根据颜色格式设置一个用于OpenGL 处理颜色转换的矩阵。
1 |
|
接下来就是处理视频原始数据了。首先判断了视频的颜色格式,如果属于yuv格式的,则创建两个CVOpenGLESTextureRef用于接受y分量和uv分量的纹理。 在CVOpenGLESTextureRef的定义出可以看出CVOpenGLESTextureRef实际上就是CVImageBufferRef的别名。
@typedef CVOpenGLESTextureRef @abstract OpenGLES texture based image buffer
typedef CVImageBufferRef CVOpenGLESTextureRef;
接下来就是调用CVOpenGLESTextureCacheCreateTextureFromImage这个方法将y分量和uv分量分别写入两个纹理并与OpenGL的纹理绑定。然后就是调用方法convertYUVToRGBOutput来将yuv数据转换成RGB数据并放入帧缓冲中,并且作为纹理存放在framebuffer的_texture中。然后调用updateTargetsForVideoCameraUsingCacheTextureAtWidth方法,将帧缓冲交给响应链的下一个对象处理。
1 |
|
视频数据是RGB的情况的话,就直接将数据写入outputFramebuffer的纹理中,然后执行响应链后续的处理。而响应链的处理,就是将outputFramebuffer设置为下一对象的inputFramebuffer,然后执行newFrameReadyAtTime:atIndex: 方法做下一步处理,这个方法具体的处理在上一篇笔记中有过分析。
分析GPUImageView
在响应链中,经过filter的处理后,GPUImageView作为处理的终点,将画面渲染出来。
1 |
|
可以看到,GPUImageView,基本上就是使用OpenGL实现了一个用于渲染RGBA数据的view。在初始化时,首先将自身的layer设置为CAEAGLLayer,然后创建一个用于渲染显示的program,链接并使用它。之后再配置一些输入参数。最后使用createDisplayFramebuffer方法创建一个用于显示的帧缓冲。
由于GPUImageView也实现了GPUImageInput协议,因此也实现了newFrameReadyAtTime: atIndex:方法,用于处理接收到的视频数据。
1 |
|
这里就是实现了OpenGL的渲染过程,首先在setDisplayFramebuffer方法中绑定用于显示的帧缓冲,然后将inputFramebufferForDisplay的纹理绑定并作为参数传入片段着色器,然后传入顶点和纹理坐标,调用glDrawArrays进行绘制,最后调用presentFramebuffer方法,使用renderbuffer渲染画面。
补充,获取滤镜处理后的数据
1 |
|
最开始调用的代码,只是将采集视频经过滤镜处理后,渲染显示在GPUImageView上,而实际应用时,一般需要将处理后的数据发送出去,而不是简单地在本地显示。获取处理后的数据,就需要用到GPUImageRawDataOutput。
GPUImageRawDataOutput也是一个实现GPUImageInput协议的类,也可以作为响应链中的target,然后给它的newFrameAvailableBlock赋值,在block中通过rawBytesForImage方法取得数据。
1 |
|
可以看到GPUImageRawDataOutput在newFrameReadyAtTime:atIndex:方法中执行_newFrameAvailableBlock这个block,用于每次取得渲染好的数据后回调。
总结
GPUImage的视频实时滤镜处理实际上就是一个响应链由GPUImageVideoCamera为起点,经过一系列filter的处理后,由GPUImageView或GPUImageRawDataOutput作为链条的终点。并且也可以看到GPUImage中有些对OpenGL的调用使用了CoreVideo框架的接口,后续也可以研究一下。