Android 使用 camera2 录像
本文讲解了在 Android 中如何使用 camera2 录像。
日记暂时记录
预览步骤
1 - SurfaceView.getHolder.surfaceCreated »> Surface 创建时进行配置
2 - StreamConfigurationMap.isOutputSupportedFor(SurfaceHolder::class.java) »> 判断视频流是否支持输出到 SurfaceHolder
3 - context.getSystemService(Context.CAMERA_SERVICE) »> 获取 CameraManager 类
4 - CameraManager.getCameraIdList() 和 CameraManager.getCameraCharacteristics(cameraId) »> 获取所有的相机列表,并筛选出其中可用的相机
val cameraIds = cameraIdList.filter {
val characteristics = cameraManager.getCameraCharacteristics(it)
val capabilities = characteristics.get(
CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES
)
// 过滤出向后兼容(又叫向下兼容,兼容旧代码)的功能集
capabilities?.contains(
CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE
) ?: false
}
5 - CameraCharacteristics.get(key) »> 使用诸如相机朝向等信息筛选出目标相机:
val facing = characteristics.get(
CameraCharacteristics.LENS_FACING
) ?: CameraMetadata.LENS_FACING_FRONT
val facingDesc = lensOrientationString(facing)
// 返回相机朝向的说明文本
private fun lensOrientationString(
facing: Int
) = when(facing) {
CameraCharacteristics.LENS_FACING_BACK -> "Back"
CameraCharacteristics.LENS_FACING_FRONT -> "Front"
CameraCharacteristics.LENS_FACING_EXTERNAL -> "External"
else -> "Unknown"
}
3/4/5 步和 1/2 步可以并行
6 - CameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) »> 获取 StreamConfigurationMap 配置集
7 - StreamConfigurationMap.getOutputSizes »> 获取输出流支持的尺寸,并筛选出支持的可用尺寸
// 根据尺寸的面积从小到大排序
val validSizes = allSizes
.sortedBy { it.height * it.width }
// 获取小于等于目标最大值的尺寸
return validSizes.first { it.long <= maxSize.long && it.short <= maxSize.short }.size
8 - 初始化 EGL 配置
8.1 - EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY) »> 获取默认的 EGL 输出
private var eglDisplay = EGL14.EGL_NO_DISPLAY
eglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY)
if (eglDisplay == EGL14.EGL_NO_DISPLAY) {
throw RuntimeException("unable to get EGL14 display")
}
8.2 - EGL14.eglInitialize »> 初始化 EGL
val version = intArrayOf(0, 0)
if (!EGL14.eglInitialize(eglDisplay, version, 0, version, 1)) {
eglDisplay = null
throw RuntimeException("unable to initialize EGL14")
}
8.3 - EGL14.eglChooseConfig »> 确定合适的 EGL 属性
- EGL_RENDERABLE_TYPE 表示 EGL 配置支持的使用 eglCreateContext 函数创建的 client API 上下文的类型,此处设置为 OPENGL_ES 2。
- EGL_RED_SIZE/EGL_GREEN_SIZE/EGL_BLUE_SIZE/EGL_ALPHA_SIZE 表示 RGBA 颜色缓冲区中各颜色分量的颜色位数,此处颜色格式设置为 ARGB_8888。
- EGL_DEPTH_SIZE 表示所需的 depth buffer(深度缓冲区)的尺寸(单位为 bit)。
- EGL_STENCIL_SIZE 表示所需的 stencil buffer (模板缓冲区)的尺寸(单位为 bit)。
- EGL_RECORDABLE_ANDROID 表示 Android 指定的标志。此标志告诉 EGL 它创建的 surface 必须和视频编解码器兼容。没有这个标志,EGL 可能会使用一个 MediaCodec 不能理解的 Buffer。这个变量在 api 26 以后系统才自带有。
- EGL_NONE 表示属性列表的数组结束标记。
// 我们需要的 EGL 配置列表
val configAttribList = intArrayOf(
EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
EGL14.EGL_RED_SIZE, 8,
EGL14.EGL_GREEN_SIZE, 8,
EGL14.EGL_BLUE_SIZE, 8,
EGL14.EGL_ALPHA_SIZE, 8,
EGL14.EGL_DEPTH_SIZE, 0,
EGL14.EGL_STENCIL_SIZE, 0,
EGLExt.EGL_RECORDABLE_ANDROID, 1,
EGL14.EGL_NONE
)
// 用于接收结果的列表
val configs = arrayOfNulls<EGLConfig>(1)
// 查询结果的数量
val numConfigs = intArrayOf(1)
// 查询 EGL 属性
EGL14.eglChooseConfig(eglDisplay, configAttribList, 0, configs,
0, configs.size, numConfigs, 0)
eglConfig = configs[0]
if (eglConfig == null) {
eglDisplay = null
throw RuntimeException("unable to initialize EGL14")
}
8.4 - EGL14.eglCreateContext »> 创建 EGLContext
- EGL_CONTEXT_CLIENT_VERSION 用于指定与我们所使用的 OpenGL ES 版本相关的上下文类型。默认值是1(即指定 OpenGL ES 1.X 版本的上下文类型),此处指定为 2,表示 OpenGL ES 2.x 版本。
val contextAttribList = intArrayOf(
EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
EGL14.EGL_NONE
)
eglContext = EGL14.eglCreateContext(eglDisplay, eglConfig, EGL14.EGL_NO_CONTEXT,
contextAttribList, 0)
if (eglContext == EGL14.EGL_NO_CONTEXT) {
// 执行变量重置操作
throw RuntimeException("Failed to create EGL context")
}
8.5 - EGL14.eglCreatePbufferSurface »> 创建离屏像素缓冲区表面(off-screen pixel buffer surface)并返回其句柄。
val tmpSurfaceAttribs = intArrayOf(
EGL14.EGL_WIDTH, 1,
EGL14.EGL_HEIGHT, 1,
EGL14.EGL_NONE
)
val tmpSurface = EGL14.eglCreatePbufferSurface(
eglDisplay,
eglConfig,
tmpSurfaceAttribs,
/*offset*/ 0
)
8.6 - EGL14.eglMakeCurrent »> 将 EGL 渲染上下文添加到离屏 surface。
EGL14.eglMakeCurrent(eglDisplay, tmpSurface, tmpSurface, eglContext)
8.7 - EGL14.eglCreateWindowSurface »> 用于创建一个在屏幕上渲染的 EGL window surface 并返回它的句柄。
val surfaceAttribs = intArrayOf(EGL14.EGL_NONE)
eglWindowSurface = EGL14.eglCreateWindowSurface(
eglDisplay,
eglConfig,
/*SurfaceHolder 的 Surface*/ surface,
surfaceAttribs,
/*offset*/ 0
)
if (eglWindowSurface == EGL14.EGL_NO_SURFACE) {
throw RuntimeException("Failed to create EGL texture view surface")
}
8.8 - EGL14.eglMakeCurrent »> 将 EGL 渲染上下文添加到 window surface。
EGL14.eglMakeCurrent(eglDisplay, eglWindowSurface, eglWindowSurface, eglContext)
9 - 初始化用于相机的 OpenGL ES 配置
9.1 - 创建提供给相机的 OpenGL Texture。
- GL_TEXTURE_EXTERNAL_OES 是一个特殊的纹理目标,它是 OpenGL ES 的一个扩展,主要用于访问不直接存储在 OpenGL ES 内存中的纹理,例如视频帧或者相机预览帧等。这个 纹理目标 与常见的 GL_TEXTURE_2D 纹理目标 类似,但是它有一些特殊的限制和特性,例如它不支持 mipmap,只支持 GL_LINEAR 和 GL_NEAREST 两种纹理过滤方式,等等。
- GLES20.glTexParameteri 函数用于设置纹理参数。纹理参数用于控制纹理对象的一些属性,例如纹理过滤方式、纹理环绕方式等。
- GL_TEXTURE_MIN_FILTER 表示要设置的纹理参数。此处我们设置的是纹理对象的缩小过滤方式(minification filter)。缩小过滤方式用于控制当纹理被缩小时,如何从纹理图像中采样颜色。
- GL_TEXTURE_MAG_FILTER 纹理放大过滤方式(magnification filter)。放大过滤方式用于控制当纹理被放大时,如何从纹理图像中采样颜色。
- GL_NEAREST 表示要设置的纹理参数的值。GL_NEAREST 表示使用最近邻过滤,即从纹理图像中选择距离采样点最近的纹理像素(texel)的颜色。这种过滤方式速度较快,但是在纹理被显著缩小时,可能会导致锯齿状的边缘和不平滑的过渡。
- GL_LINEAR 线性过滤表示在纹理图像中选择距离采样点最近的四个纹理像素(texel)进行双线性插值,以获得更平滑的颜色过渡。这种过滤方式在纹理被放大时,可以得到更好的视觉效果,但是速度稍慢。
- GL_TEXTURE_WRAP_S & GL_TEXTURE_WRAP_T 表示纹理 S轴(水平轴) 和 T轴(垂直轴) 的环绕方式。
- GL_CLAMP_TO_EDGE 表示边缘延伸。边缘延伸表示当纹理坐标超出[0, 1]范围时,纹理将使用边缘的颜色进行填充。这种环绕方式可以避免纹理边缘的颜色与相邻边缘的颜色混合,从而避免出现不希望的边缘缝隙。
cameraTexId = createTexture()
private fun createTexture(): Int {
// 检查 EGL 是否初始化 OK
if (eglDisplay == null) {
throw IllegalStateException("EGL not initialized before call to createTexture()");
}
// 获取存储 buffer id 的数组
val bufferId = IntBuffer.allocate(1)
// 生成数量为 1 的纹理对象,bufferId 的尺寸要 >= glGenTextures 函数的第一个参数
GLES20.glGenTextures(1, bufferId)
val texId = bufferId.get(0)
// 将一个纹理对象 texId 绑定到 GL_TEXTURE_EXTERNAL_OES 纹理目标上,以便对这个纹理对象进行操作
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texId)
// 设置当前绑定到 GL_TEXTURE_EXTERNAL_OES 纹理目标的纹理对象的最小过滤方式为最近邻过滤
GLES20.glTexParameteri(
GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
GLES20.GL_TEXTURE_MIN_FILTER,
GLES20.GL_NEAREST
)
// 绑定到 GL_TEXTURE_EXTERNAL_OES 纹理目标的纹理对象的纹理放大过滤方式为线性过滤
GLES20.glTexParameteri(
GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
GLES20.GL_TEXTURE_MAG_FILTER,
GLES20.GL_LINEAR
)
// 绑定到 GL_TEXTURE_EXTERNAL_OES 纹理目标的纹理对象的S轴(水平轴)的纹理环绕方式为边缘延伸
GLES20.glTexParameteri(
GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
GLES20.GL_TEXTURE_WRAP_S,
GLES20.GL_CLAMP_TO_EDGE
)
// 绑定到 GL_TEXTURE_EXTERNAL_OES 纹理目标的纹理对象的T轴(垂直轴)的纹理环绕方式为边缘延伸
GLES20.glTexParameteri(
GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
GLES20.GL_TEXTURE_WRAP_T,
GLES20.GL_CLAMP_TO_EDGE
)
return texId
}
9.2 - 根据 textureId 创建表面纹理(SurfaceTexture)
cameraTexture = SurfaceTexture(cameraTexId)
9.3 - SurfaceTexture.setOnFrameAvailableListener »> 设置视频帧可用时的监听
cameraTexture.setOnFrameAvailableListener(this)
9.4 - 设置输出帧的尺寸,如 1080 x 1920。
cameraTexture.setDefaultBufferSize(width, height)
9.5 - 根据 SurfaceTexture 创建 Surface。
cameraSurface = Surface(cameraTexture)
10 - 初始化用于存储渲染结果的 OpenGL ES 配置。相关讲解第 9 节的讲解。
10.1 - 创建纹理对象、SurfaceTexture、Surface
renderTexId = createTexture()
renderTexture = SurfaceTexture(renderTexId)
renderTexture.setDefaultBufferSize(width, height)
renderSurface = Surface(renderTexture)
10.2 - 创建与 renderSurface 关联的 window surface。
eglRenderSurface = EGL14.eglCreateWindowSurface(
eglDisplay,
eglConfig,
renderSurface,
surfaceAttribs,
0
)
if (eglRenderSurface == EGL14.EGL_NO_SURFACE) {
throw RuntimeException("Failed to create EGL render surface")
}
10.3 - 检查着色器和着色器程序是否初始化 OK
if (passthroughShaderProgram == null) {
createShaderResources()
}
private fun createShaderResources() {
vertexShader = createShader(GLES20.GL_VERTEX_SHADER, TRANSFORM_VSHADER)
passthroughFragmentShader = createShader(GLES20.GL_FRAGMENT_SHADER, PASSTHROUGH_FSHADER)
portraitFragmentShader = createShader(GLES20.GL_FRAGMENT_SHADER, PORTRAIT_FSHADER)
passthroughShaderProgram = createShaderProgram(passthroughFragmentShader)
portraitShaderProgram = createShaderProgram(portraitFragmentShader)
}
11 - 初始化相机