iOS 使用 OpenGLES 实现相机画面镜像
前言
上一篇 使用 OpenGLES 渲染相机预览画面 实现了自定义相机画面渲染,使用自定义的图层替代了 AVFoundation 默认渲染图层,但还需要考虑的是预览和编码镜像的问题。由于现在图层使用的 Buffer 数据来自 AVCaptureVideoDataOutput ,我们可以通过设置 AVCaptureVideoDataOutput 链接的 AVCaptureConnection 的 videoMirrored 属性,并关闭 automaticallyAdjustsVideoMirroring 去统一调整预览和编码的镜像,但有些场景需要预览镜像编码不镜像,或预览不镜像编码镜像,所以就需要一个工具类去处理预览和编码镜像不一致的场景。
实现思路
大批量的像素翻转不适合在 CPU 上处理,因此考虑使用 OpenGL 的离屏渲染,将输出纹理绑定在帧缓冲区的颜色缓冲,在输入纹理绑定上下文后,通过翻转顶点着色器 gl_Position 的 X 坐标实现纹理镜像。
代码
首先是顶点坐标的计算,自定义一个 VerticesCoordinates,positionCoordinates 表示顶点坐标,textureCoordinates 表示纹理坐标,vertices 中的 4 个坐标分别是矩形的 4 个顶点,这里跟上一篇的顶点坐标有点不同,上一篇的顶点坐标用于屏幕渲染,会有纹理原点和屏幕原点不一致的情况,所以纹理坐标的 Y 做了翻转,我们这次的输出也是纹理,所以不需要做翻转,一一对应就可以。我们绘制用的是 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);,即 4 个顶点绘制 2 个三角形。
1 | typedef struct { |
为了方便,顶点着色器和片段着色器保存在字符串里。顶点着色器定义两个 attribute 属性用来读取顶点坐标和纹理坐标,v_texcoord 属性将纹理坐标传递给片段着色器,在 main 函数里,将 gl_Position 的 X 坐标做了翻转来实现镜像效果。片段着色器先声明了 float 使用中等精度,v_texcoord 与顶点着色器对应,tex 声明一个纹理采样器,最后在 main 函数里使用纹理采样器计算出像素颜色。
1 | // vertex shader |
着色器的创建、编译和链接,着色器程序的生成代码:
1 | static inline GLuint compile_shader(GLuint type, const char* source) { |
着色器和顶点坐标准备完成后,可以初始化 OpenGL 环境了:
1 | - (void)setupGL { |
由于帧缓冲区输出的纹理依赖输入的 Pixel Buffer 的宽高,所以 fbo 和输出纹理放到 process 方法里动态创建。在初始化 OpenGL 环境后,就可以调用 process 来渲染了:
1 | - (CVPixelBufferRef)process:(CVPixelBufferRef)pixelBuffer { |
27 行位置判断当前输入的 Pixel Buffer 宽高是否与上一次渲染的宽高一致,如果不一致会重新创建输出的 Pixel Buffer:
1 | - (void)setupCVPixelBuffer:(CVPixelBufferRef *)pixelBuffer { |
随后会重新初始化帧缓冲区和输出纹理:
1 | - (void)setupFBO { |
setupFBO 创建了帧缓冲,并将输出纹理绑定到帧缓冲的颜色缓冲上,处理完成后将 _outputPixelBuffer 返回给调用者。
另外还有一些前后台的处理:
1 | - (void)handleApplicationDidEnterBackground:(NSNotification *)notification { |
后记
代码完成后,就可以在 didOutputSampleBuffer 回调里使用了,这样,前后置预览镜像和编码镜像就都可以单独配置了,镜像后的数据可以输入到下一级的 processor 中做处理,最终吐给编码器编码。