前言:
-
在上一篇文章中给小伙伴们介绍了进行Camera预览,如果你还没有看过的话,建议先去看上一篇文章《Android 短视频开发之摄像头预览(二》
-
本篇文章会介绍如何实现摄像头预览画面实时美颜
原理:
废话就不多啰嗦了,要达到 摄像头实时美颜的效果,从原理上来讲很简单,只要保证每一帧画面经过美颜处理后显示在界面上就完了。
实现思路:
前面的预览过程就不罗嗦了,不懂可以看上一篇文章。
这里有两种方式实现
- 在Camera.PreviewCallback回调中直接获取图片数据,然后经过OpengGL处理后再显示出来
- 直接让Camera的预览画面渲染在OpengGL的OES纹理中,通过SurfaceTexture把数据交给OpengGL渲染后再渲染到屏幕上
当然第一种缺点很明显就是渲染速度太慢会导致预览异常卡顿,帧率也达不到要求,主要问题在于摄像头获取的数据时YUV格式的数据而,OpenGL渲染是RGBA格式的数据,这就导致要渲染首先要先转换颜色空间,这个转换时比较耗时的。二第二种方式刚好能规避这个问题,直接把这已转换过程让GPU做了,速度非常快。预览和渲染效果很好,而且帧率也能达到要求。所以一般美颜相机类应用都会选择第二种方式来处理。
在这里由于后面的模块都涉及到OpengL渲染,所以需要有一定的OpenGL经验,建议对OpenGL不熟的先学习下基础。网上关于OpenGL的文章有很多这里就不列举了。
具体实现
1.创建OpenGL渲染环境,因为使用的时TextureView所以需要自己创建
/**
* Surface 要显示的TextureView中的surface
*/
void surfaceCreated(SurfaceTexture surface) {
//创建OpenGL环境
mEglCore = new EglCore(null, EglCore.FLAG_RECORDABLE);
mDisplaySurface = new WindowSurface(mEglCore, surface);
//切换到当前上下文环境中
mDisplaySurface.makeCurrent();
GLES30.glDisable(GLES30.GL_DEPTH_TEST);
GLES30.glDisable(GLES30.GL_CULL_FACE);
// 渲染器初始化
mRenderManager.init(mContext);
//创建OES纹理
mInputTexture = OpenGLUtils.createOESTexture();
//创建SurfaceTexture供Camera.setPreviewTexture()
//这里时关键把摄像头的数据映射到SurfaceTexture的纹理当中供后面渲染
mSurfaceTexture = new SurfaceTexture(mInputTexture);
mSurfaceTexture.setOnFrameAvailableListener(this);
// 打开相机
openCamera();
Log.d("RenderThread","onSurfaceTextureAvailable="+surface);
}
2.接收SurfaceTexture的OnFrameAvailableListener的回调
@Override
public void onFrameAvailable(SurfaceTexture surfaceTexture) {
requestRender();
}
3.在接收到数据变化后就开始渲染数据并显示到屏幕
/**
* 绘制帧
*/
void drawFrame() {
if (mSurfaceTexture == null || mDisplaySurface == null) {
return;
}
// 当记录的请求帧数不为时,更新画面
while (mFrameNum > 0) {
// 切换渲染上下文
mDisplaySurface.makeCurrent();
//更新图像数据
mSurfaceTexture.updateTexImage();
mSurfaceTexture.getTransformMatrix(mMatrix);
--mFrameNum;
// 绘制渲染
mCurrentTexture = mRenderManager.drawFrame(mInputTexture, mMatrix);
// 显示到屏幕
mDisplaySurface.swapBuffers();
}
}
关键步骤
// 绘制渲染 mCurrentTexture = mRenderManager.drawFrame(mInputTexture, mMatrix);
在这个渲染管理器里完成了图像的渲染
drawFrame干了些什么事情呢?
/**
* 绘制纹理
* @param inputTexture
* @param mMatrix
* @return
*/
public int drawFrame(int inputTexture, float[] mMatrix) {
int currentTexture = inputTexture;
if (mFilterArrays.get(VideoRenderIndex.CameraIndex) == null
|| mFilterArrays.get(VideoRenderIndex.DisplayIndex) == null) {
return currentTexture;
}
if (mFilterArrays.get(VideoRenderIndex.CameraIndex) instanceof GLImageOESInputFilter) {
((GLImageOESInputFilter)mFilterArrays.get(VideoRenderIndex.CameraIndex)).setTextureTransformMatrix(mMatrix);
}
currentTexture = mFilterArrays.get(VideoRenderIndex.CameraIndex)
.drawFrameBuffer(currentTexture, mVertexBuffer, mTextureBuffer);
// 如果处于对比状态,不做处理
if (!mCameraParam.showCompare) {
// 美颜滤镜
if (mFilterArrays.get(VideoRenderIndex.BeautyIndex) != null) {
if (mFilterArrays.get(VideoRenderIndex.BeautyIndex) instanceof IBeautify
&& mCameraParam.beauty != null) {
((IBeautify) mFilterArrays.get(VideoRenderIndex.BeautyIndex)).onBeauty(mCameraParam.beauty);
}
currentTexture = mFilterArrays.get(VideoRenderIndex.BeautyIndex).drawFrameBuffer(currentTexture, mVertexBuffer, mTextureBuffer);
}
//LUT 颜色查找表滤镜
if (mFilterArrays.get(VideoRenderIndex.LookupFilterIndex) != null) {
if(mFilterArrays.get(VideoRenderIndex.LookupFilterIndex) instanceof GLImage512TwoInputLookupTableFilter){
((GLImage512TwoInputLookupTableFilter) mFilterArrays.get(VideoRenderIndex.LookupFilterIndex)).updateBitmap();
}
currentTexture = mFilterArrays.get(VideoRenderIndex.LookupFilterIndex).drawFrameBuffer(currentTexture, mVertexBuffer, mTextureBuffer);
}
}
// 显示输出,需要调整视口大小
mFilterArrays.get(VideoRenderIndex.DisplayIndex).drawFrame(currentTexture, mDisplayVertexBuffer, mDisplayTextureBuffer);
return currentTexture;
}
其实这里就到了滤镜渲染了。对每帧图片进行了渲染处理。每一个滤镜渲染完交给下一个滤镜渲染达到滤镜组合显示的效果
到此就完成了相机预览画面实时渲染了。