第一章,使用SurfaceTexture作为Camera输出
在Android系统中,使用GPU对摄像头画面进行高效可控的渲染,几乎是必须的。说到GPU就不得不提OpenGL,一组GPU暴露给应用层使用的接口。
CameraPreviewPresenter.png
UML类图
正式开始之前我们先看一下这个模块的脑图和类图,以便对结构有个大概的了解。
首先,CameraPreviewPresenter需要实现SurfaceTexture.OnFrameAvailableListener接口,并且持有一个Parameter和一个CameraWrapper。cameraWrapper 会在init中初始化,parameter则是在CameraPreviewPresenter的调用处传进来。
class CameraPreviewPresenter(var parameter: Parameter,
private var cameraWrapper: CameraWrapper? = null)
: SurfaceTexture.OnFrameAvailableListener{
init {
cameraWrapper = CameraWrapper.open(parameter, this)
}
}
接下来在CameraWrapper里面,我们同样看到了Parameter,这个类会贯穿整个结构层级。onFrameAvailableListener也是由外部调用者赋值进来的,也就是CameraPreviewPresenter。
由于Camera的初始化是一个耗时任务,所以需要在线程中初始化,这里使用了一个HandlerThread。熟悉HandlerThread的人应该知道,HandlerThread会和一个Handler关联起来,通过Handler给Thread发送消息,从而实现子线程中的消息消费,具体的效果就是可以让线程以规定的顺序间歇性的处理我们的任务,而无须关心线程的等待问题。
class CameraWrapper(private var parameter: Parameter,
private var onFrameAvailableListener: SurfaceTexture.OnFrameAvailableListener,
var textureWrapper: TextureWrapper = CameraTextureWrapper()) {
private var mHandlerThread = HandlerThread("Renderer_Thread")
private var mHandler: Handler? = null
private var mCamera: Camera? = null
private var mCameras = 0
private var mCameraIndex = Camera.CameraInfo.CAMERA_FACING_BACK
}
init {
mCameras = CameraHelper.getNumberOfCameras()
initThread()
mHandler?.removeMessages(PREPARE)
mHandler?.sendEmptyMessage(PREPARE)
}
private fun initThread() {
mHandlerThread.start()
mHandler = object : Handler(mHandlerThread.looper) {
override fun handleMessage(msg: Message) {
when (msg.what) {
PREPARE -> {
prepare()
}
}
}
}
}
private fun prepare() {
if (0 == mCameras) {
debug_e("Unavailable camera")
return
}
//如果没有前置摄像头,则强制使用后置摄像头
if (parameter.cameraIndex == Camera.CameraInfo.CAMERA_FACING_FRONT && mCameras < 2)
parameter.cameraIndex = Camera.CameraInfo.CAMERA_FACING_BACK
mCameraIndex = parameter.cameraIndex
val time = System.currentTimeMillis()
mCamera = openCamera(mCameraIndex)
debug_e("open time: ${System.currentTimeMillis() - time}")
if (null == mCamera) return
val cameraParam = mCamera!!.parameters
CameraHelper.setPreviewSize(cameraParam, parameter)
CameraHelper.setColorFormat(cameraParam, parameter)
CameraHelper.setFocusMode(cameraParam, parameter)
CameraHelper.setFps(cameraParam, parameter)
CameraHelper.setAutoExposureLock(cameraParam, false)
CameraHelper.setSceneMode(cameraParam, Camera.Parameters.SCENE_MODE_AUTO)
CameraHelper.setFlashMode(cameraParam, Camera.Parameters.FLASH_MODE_OFF)
CameraHelper.setAntibanding(cameraParam, Camera.Parameters.ANTIBANDING_AUTO)
CameraHelper.setVideoStabilization(cameraParam, true)
val fps = IntArray(2)
cameraParam.getPreviewFpsRange(fps)
debug_v("Config: Size(${parameter.previewWidth}x${parameter.previewHeight})\n" +
"Format(${cameraParam.previewFormat})\n" +
"FocusMode(${cameraParam.focusMode})\n" +
"Fps(${fps[0]}-${fps[1]})\n" +
"AutoExposureLock(${cameraParam.autoExposureLock})\n" +
"SceneMode(${cameraParam.sceneMode})\n" +
"FlashMode(${cameraParam.flashMode})\n" +
"Antibanding(${cameraParam.antibanding})\n" +
"VideoStabilization(${cameraParam.videoStabilization})")
try {
mCamera!!.parameters = cameraParam
mPrepare = true
if (mRequestPreview) {
startPreview()
mRequestPreview = false
mPrepare = false
}
} catch (e: Exception) {
mCamera!!.release()
debug_e("Camera $mCameraIndex open failed. Please check parameters")
}
}
fun setPreviewSize(cameraParam: Camera.Parameters, videoParam: Parameter) {
val supportSizes = cameraParam.supportedPreviewSizes
var bestWidth = 0
var bestHeight = 0
for (size in supportSizes) {
if (size.width >= videoParam.previewWidth//预览宽大于输出宽
&& size.height >= videoParam.previewHeight//预览高大于输出高
&& (size.width * size.height < bestWidth * bestHeight || 0 == bestWidth * bestHeight)) {//选择像素最少的分辨率
bestWidth = size.width
bestHeight = size.height
}
}
debug_v("target preview size: " + videoParam.previewWidth + "x" + videoParam.previewHeight + ", best: " + bestWidth + "x" + bestHeight)
videoParam.previewWidth = bestWidth
videoParam.previewHeight = bestHeight
videoParam.check()
cameraParam.setPreviewSize(videoParam.previewWidth, videoParam.previewHeight)
}
startPreview
。这里比较重要的一点就是预览输出方向为SurfaceTexture,也就是OpenGL的一个纹理,而不是屏幕,我们可以把它看作一个缓冲区。SurfaceTexture的创建被包含在CameraTextureWrapper中。 fun startPreview(): Boolean {
mRequestPreview = true
if (!mPrepare) return false
if (null == mCamera) return false
textureWrapper.surfaceTexture!!.setOnFrameAvailableListener(onFrameAvailableListener)
return try {
mCamera!!.setPreviewTexture(textureWrapper.surfaceTexture)
mCamera!!.startPreview()
true
} catch (e: Exception) {
release()
debug_e("Start preview failed")
e.printStackTrace()
false
}
}
CameraTextureWrapper的初始化,先来看一下它的抽象父类TextureWrapper。很简单,就是一些基础代码,当然包含了一些必要的属性
abstract class TextureWrapper(open var surfaceTexture: SurfaceTexture? = null,
var texture: BaseTexture? = null,
open var textureId: Int? = null,
var egl: Egl? = null) {
abstract fun drawTexture(transformMatrix: FloatArray?)
open fun release() {
if (null != surfaceTexture) {
surfaceTexture!!.release()
surfaceTexture = null
}
}
}
由于使用OpenGL生成TextureId会有一些未知的错误,所以这里直接给定textureId=10,然后new一个SurfaceTexture。
init {
/**
* 使用createTexture()会一直返回0,导致一些错误
*/
textureId = 10
intTexture()
}
private fun intTexture() {
if (null != textureId)
surfaceTexture = SurfaceTexture(textureId!!)
}
EGL的初始化,由于egl需要在同一个线程中初始化,否则会报错,所以这里只给出了一个外部调用的方法,在适当时刻才初始化EGL。
fun initEGL(width: Int, height: Int) {
egl = Egl()
egl!!.initEGL()
egl!!.makeCurrent()
texture = CameraTexture(width, height, textureId!!)
debug_e("camera textureId: ${textureId}")
}
接下来是一个很重要的类CameraTexture,这是一套OpenGL纹理绘制的封装,在之后的很多地方都会用到,包括滤镜渲染,屏幕绘制。
首先生成一组纹理的定点数据verticesBuffer
,然后便宜链接OpenGL的定点和片元程序createProgram
,并且保存程序中的一些变量位置,以便在drawTexture
中给他们指定参数。
在drawTexture(transformMatrix: FloatArray?) 中我们可以看到OpenGL一系列的状态切换。重要!重要!重要!
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, frameBuffer!!)
GLES20.glUseProgram(shaderProgram!!)
GLES20.glActiveTexture(GLES20.GL_TEXTURE0)
GL_TEXTURE_EXTERNAL_OES
、GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureId)
,这里需要注意的是,这个textureId跟1.中的FBO是成对的,一个进,一个出。GLES20.glUniform1i(uTextureLocation, 0)
enableVertex(aPositionLocation, aTextureCoordinateLocation, buffer!!, verticesBuffer!!)
GLES20.glUniformMatrix4fv(uTextureMatrix, 1, false, transformMatrix, 0)
,这个矩阵由SurfaceTexture给出,不指定的话会造成画面错乱。drawer.draw()
class CameraTexture(width: Int, height: Int,
textureId: Int) : BaseFrameBufferTexture(width, height, textureId) {
init {
verticesBuffer = createShapeVerticesBuffer(CAMERA_TEXTURE_VERTICES)
createProgram()
initFrameBuffer()
}
private fun createProgram() {
shaderProgram = createProgram(VERTEX_SHADER, FRAGMENT_SHADER)
aPositionLocation = getAttribLocation("aPosition")
uTextureLocation = getUniformLocation("uTexture")
aTextureCoordinateLocation = getAttribLocation("aTextureCoord")
uTextureMatrix = getUniformLocation("uTextureMatrix")
}
override fun drawTexture(transformMatrix: FloatArray?) {
if (null == transformMatrix)
throw RuntimeException("TransformMatrix can not be null")
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, frameBuffer!!)
GLES20.glUseProgram(shaderProgram!!)
GLES20.glActiveTexture(GLES20.GL_TEXTURE0)
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureId)
GLES20.glUniform1i(uTextureLocation, 0)
enableVertex(aPositionLocation, aTextureCoordinateLocation, buffer!!, verticesBuffer!!)
GLES20.glUniformMatrix4fv(uTextureMatrix, 1, false, transformMatrix, 0)
drawer.draw()
GLES20.glFinish()
GLES20.glDisableVertexAttribArray(aPositionLocation)
GLES20.glDisableVertexAttribArray(aTextureCoordinateLocation)
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_NONE)
GLES20.glUseProgram(GLES20.GL_NONE)
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, GLES20.GL_NONE)
}
}
BaseFrameBufferTexture是CameraTexture的父类,也是BaseTexture的扩展子类,增加了对FBO的支持。所以在这里自然是对FBO进行初始化,大致的流程是:
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, width, height, 0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null)
,这里的width和height其实就是我们要输出的视频的大小,之后我们会使用OpenGL的glViewport
指定画面区域,达到裁剪的目的。GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR.toFloat())
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR.toFloat())
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE.toFloat())
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE.toFloat())
abstract class BaseFrameBufferTexture(var width: Int,
var height: Int,
textureId: Int,
var frameBuffer: Int? = null,
var frameBufferTexture: Int? = null) : BaseTexture(textureId) {
fun initFrameBuffer() {
val frameBuffer = IntArray(1)
val frameBufferTex = IntArray(1)
GLES20.glGenFramebuffers(1, frameBuffer, 0)
GLES20.glGenTextures(1, frameBufferTex, 0)
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, frameBufferTex[0])
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, width, height, 0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null)
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR.toFloat())
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR.toFloat())
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE.toFloat())
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE.toFloat())
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, frameBuffer[0])
GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, frameBufferTex[0], 0)
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0)
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0)
val error = GLES20.glGetError()
if (error != GLES20.GL_NO_ERROR) {
val msg = "initFrameBuffer: glError 0x" + Integer.toHexString(error)
debug_e(msg)
return
}
this.frameBuffer = frameBuffer[0]
this.frameBufferTexture = frameBufferTex[0]
debug_e("enable frame buffer: ${this.frameBuffer}, ${this.frameBufferTexture}")
}
}
在BaseTexture中主要处理顶点和片元程序的编译链接,还有顶点坐标的生成。这里时固定化的流程,所有的OpenGL程序都是按照这个流程生成。
abstract class BaseTexture(var textureId: Int,
var buffer: FloatBuffer? = null,
var verticesBuffer: FloatBuffer? = null,
var shaderProgram: Int? = null,
var drawer: GLDrawer = GLDrawer()) : Texture {
companion object {
var COORDS_PER_VERTEX = 2
var TEXTURE_COORDS_PER_VERTEX = 2
private val DRAW_INDICES = shortArrayOf(0, 1, 2, 0, 2, 3)
//每行前两个值为顶点坐标,后两个为纹理坐标
val VERTICES_SQUARE = floatArrayOf(
-1.0f, 1.0f,
-1.0f, -1.0f,
1.0f, -1.0f,
1.0f, 1.0f)
}
init {
buffer = createShapeVerticesBuffer(VERTICES_SQUARE)
}
fun createShapeVerticesBuffer(array: FloatArray): FloatBuffer {
val result = ByteBuffer.allocateDirect(4 * array.size).order(ByteOrder.nativeOrder()).asFloatBuffer()
result.put(array).position(0)
return result
}
fun createProgram(vertex: String, fragment: String): Int {
val vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertex)
val fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragment)
return linkProgram(vertexShader!!, fragmentShader!!)
}
/**
* 加载着色器,GL_VERTEX_SHADER代表生成顶点着色器,GL_FRAGMENT_SHADER代表生成片段着色器
*/
fun loadShader(type: Int, shaderSource: String): Int {
//创建Shader
val shader = GLES20.glCreateShader(type)
if (shader == 0) {
throw RuntimeException("Create Shader Failed!" + GLES20.glGetError())
}
//加载Shader代码
GLES20.glShaderSource(shader, shaderSource)
//编译Shader
GLES20.glCompileShader(shader)
return shader
}
/**
* 将两个Shader链接至program中
*/
fun linkProgram(verShader: Int, fragShader: Int): Int {
//创建program
val program = GLES20.glCreateProgram()
if (program == 0) {
throw RuntimeException("Create Program Failed!" + GLES20.glGetError())
}
//附着顶点和片段着色器
GLES20.glAttachShader(program, verShader)
GLES20.glAttachShader(program, fragShader)
//链接program
GLES20.glLinkProgram(program)
//告诉OpenGL ES使用此program
GLES20.glUseProgram(program)
return program
}
fun enableVertex(posLoc: Int, texLoc: Int, shapeBuffer: FloatBuffer, texBuffer: FloatBuffer) {
GLES20.glEnableVertexAttribArray(posLoc)
GLES20.glEnableVertexAttribArray(texLoc)
GLES20.glVertexAttribPointer(posLoc, COORDS_PER_VERTEX,
GLES20.GL_FLOAT, false,
COORDS_PER_VERTEX * 4, shapeBuffer)
GLES20.glVertexAttribPointer(texLoc, TEXTURE_COORDS_PER_VERTEX,
GLES20.GL_FLOAT, false,
TEXTURE_COORDS_PER_VERTEX * 4, texBuffer)
}
fun getAttribLocation(name: String): Int {
return GLES20.glGetAttribLocation(shaderProgram!!, name)
}
fun getUniformLocation(name: String): Int {
return GLES20.glGetUniformLocation(shaderProgram!!, name)
}
fun release() {
if (null != shaderProgram)
GLES20.glDeleteProgram(shaderProgram!!)
}
class GLDrawer(var drawIndecesBuffer: ShortBuffer? = null) {
init {
drawIndecesBuffer = ByteBuffer.allocateDirect(2 * DRAW_INDICES.size).order(ByteOrder.nativeOrder()).asShortBuffer()
drawIndecesBuffer?.put(DRAW_INDICES)
drawIndecesBuffer?.position(0)
}
fun draw() {
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f)
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
GLES20.glDrawElements(GLES20.GL_TRIANGLES, drawIndecesBuffer!!.limit(),
GLES20.GL_UNSIGNED_SHORT, drawIndecesBuffer)
}
}
流程比较多,可能讲的不是很到位,请配合源码阅读,如有错误,欢迎纠正。当然,这里可以选择更为简单的GLSurfaceView,但是在某些情况下,TextureView有不可替代的作用,详情可以去搜索一下TextureView和GLSurfaceView的区别。
至此,所有的TextureView渲染环境已经初始化完成了,之后在Render线程中初始化这个camerWrapper的EGL环境,就可以在SurfaceTexture.OnFrameAvailableListener回调中执行drawTexture方法把摄像头数据绘制到FBO中了,下一章会讲到。