渲染视频实际上就是不断地把视频数据放入纹理中,让OpenGL去渲染显示,本文会利用iOS设备采集RGBA、NV12数据,然后进行渲染显示。
利用AVFoundation采集视频
这里创建一个AVCaptureSession采集前置摄像头视频数据,格式为32BGRA,然后利用AVCaptureVideoDataOutput的代理方法获取采集到的数据。
1 |
|
AVCaptureVideoDataOutput代理方法实现
1 |
|
bufferPtr指针即指向RGBA数据的地址。
修改GLView
调用OpenGL的准备工作
这里将调用OpenGL的准备方法整合到一起,并在initWithFrame:和initWithCoder:中都调用它,这样在代码和storyboard中创建View就都会准备好OpenGL。
1 |
|
这里创建了一个串行队列,所有的OpenGL的操作都将同步在这个队列中执行。
shader程序
这里的shader程序和上一篇笔记中的shader基本一致,就是顶点着色器将顶点坐标和纹理坐标传给片段着色器,然后片段着色器根据纹理和纹理坐标算出每个像素的颜色。
1 |
|
使用视频数据创建纹理并渲染显示
1 |
|
这里首先检查了EAGLContext是否是对应的context,因为context创建可能在不同的线程,导致不对应。接下来使用glTexImage2D函数将视频数据写入纹理缓冲,其中,由于采集的数据是32BGRA,所以倒数第三个参数传入GL_BGRA。之后使用glDrawArrays函数,传入GL_TRIANGLE_STRIP进行绘制,这个参数会使OpenGL逐个使用顶点并绘制三角形,根据GL_TRIANGLE_STRIP的特点,顶点坐标和纹理坐标以’Z’字形的顺序存放就可以画出矩形。
在AVCaptureVideoDataOutput代理中将数据传给GLView
1 |
|
将一个GLView添加到Controller并真机运行后,即可看到采集的画面。
渲染yuv420p
iOS系统可采集的另一种视频格式即是NV12,也叫yuv420sp,即是y分量存在一个平面,另外两个分量交替存储在另一个分量。因为NV12相对比较少见,实际渲染时也和yuv420p有些区别,因此先实现yuv420p的渲染,而因为iOS系统不支持直接采集yuv420p,因此此处使用一个三方库,libyuv,来对NV12的数据进行转换,然后再进行渲染。
修改采集格式
1 |
|
这里可选kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange和kCVPixelFormatType_420YpCbCr8BiPlanarFullRange,都是采集NV12,区别是uv分量的取值范围不同,这里选择第一种。
使用libyuv转换为yuv420p格式数据
在videoDataOutput的代理方法中,拿到sampleBuffer,取出两个分量,然后使用libyuv的函数NV12ToI420。
1 |
|
修改shader
因为有yuv三个分量,因此就有三个纹理,片段着色器就有三个sampler2D,顶点着色器不做改动。
1 |
|
修改纹理
1 |
|
修改渲染方法,将yuv纹理传入纹理缓冲
1 |
|
之后调用方法,运行后即可看到采集的视频。
1 |
|
渲染NV12
修改shader
这里和yuv420p的区别就是传入的纹理参数是两个,第二个是uv分量,u分量从r中取,v分量从a中取。 在网上一些例子中,v分量是从g中获取的,这个应该是移动端和PC端存储数据位置的不同。
1 |
|
修改纹理
1 |
|
修改渲染方法
1 |
|
这里需要注意,uv分量的参数变为GL_LUMINANCE_ALPHA,这个参数表示按照亮度和alpha值存储纹理单元,而GL_LUMINANCE表示按照亮度值存储纹理单元,alpha值固定为1。在修改shader时也提到,v分量是从a中获取的,也就是说如果使用GL_LUMINANCE,则v分量的值会丢失。
之后调用方法,运行后即可看到采集的视频。
1 |
|
总结
视频渲染是OpenGL很重要的一个功能,在学习研究的时候也发现不少坑,主要是一些细节的参数的不同,以及移动端和PC端的差异导致的,其中看kxmovie和GPUImage的源码还是收益不少的。