概述
前几天看了Camera2的API,因此今天花了一天写了小demo来学习Camera2相关的API,因此今天写个文章记录一下
重要类
1.CameraManager类
- 通过getSystemService(Context.CAMERA_SERVICE)方法获取,使用相机的入口,同时可以通过getCameraIdList()来获取camerId,在使用摄像头之前是必须要确定一个cameraId然后才能进行接下来的操作
2.CameraCharacteristics类
- 通过CameraManager类中的如下方法获取,这个类含有很多关于camera的参数
getCameraCharacteristics(@NonNull String cameraId)
因为还没有学习过所有参数,这里我们拿可用的预览尺寸参数作为例子,获取可用的预览的尺寸可采用下面的方式
/**
* 获取当前camera的支持预览大小
*/
fun getPreviewSize(): Array? {
var characteristics = cameraManager?.getCameraCharacteristics(mCameraId)
val configs = characteristics?.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
return configs!!.getOutputSizes(SurfaceTexture::class.java)
}
这里获取到的就是一个Size的数组了
3.CameraDevice类
这个类就是具体的Camera设备了,通过CameraManager中的openCamera()来打开设备,在其第二参数的CameraDevice.StateCallback回调中可以获取,我们可以通过这类来和Camera通信,并获取Camera的数据
4.CameraCaptureSession
CameraCaptureSession主要就是负责建立一路和相机进行数据交互的会话,需要通过CameraDevice来创建
cameraDevice!!.createCaptureSession(
listOf(
Surface(captureTexture),
(imageReader!!.surface)
), captureSessionCallback, handler
)
CameraCaptureSession的获取也是在第二个参数CameraCaptureSession.StateCallback的回调方法中获取
5.CaptureRequest
这个类主要就针对CameraCaptureSession来进行一些具体的操作,每个CaptureRequest就可以看作是对这个CameraCaptureSession的一次操作,而CaptureRequest有个内部类Builder,这个类可以通过CameraDevice创建,而CaptureRequest.Builder通过build()方法即可完成CaptureRequest的创建
实现相机预览
利用TextureView相机预览的步骤如下
- 获取CameraManager
- 获取camerId
- 通过获取到的cameraId来打开一个camera设备,得到一个CameraDevice实例
- 利用TextureView获取一个SurfaceTexture,并用CameraDevice创建一个CameraCaptureSession
- 创建一个预览类型的CaptureRequest
实现拍照
利用ImageReader来读取相机返回的数据,步骤如下
- 在创建CameraCaptureSession的时候需要再传一个ImageReader的SurfaceTexture
- 创建一个拍照类型的CaptureRequest
- 在ImageReader的ImageReader.OnImageAvailableListener来获取相机返回的Image对象
更改预览尺寸
- 获取当前相机的所有可用预览尺寸
- 在创建CameraCaptureSession之前设置SurfaceTexture的默认buffer尺寸
- 然后关闭并重新创建一个CameraCaptureSession和ImageReader,因为这两个的尺寸最好保持一致
切换摄像头
关闭当前的所有CameraCaptureSession和CameraDevice,并用新的cameraId重新进行一次所有类的创建
源码
我对上述的所有功能进行了一个简单的封装
package com.tx.openglcamera.camera
import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.graphics.*
import android.hardware.camera2.*
import android.media.ImageReader
import android.os.Handler
import android.os.HandlerThread
import android.util.Log
import android.util.Size
import android.view.Surface
import android.widget.Toast
import androidx.core.app.ActivityCompat
import java.lang.Exception
/**
* create by xu.tian
* @date 2021/10/9
*/
class CameraProvider(var context: Context, var captureTexture: SurfaceTexture) {
private val tag = CameraProvider::class.java.simpleName
private var handlerThread: HandlerThread = HandlerThread("CameraProvider")
private var handler: Handler
private var mCameraId = "0"
private var cameraManager: CameraManager? = null
private var captureSession: CameraCaptureSession? = null
private var imageReader: ImageReader? = null
var previewWidth = 0
var previewHeight = 0
private var cameraDevice: CameraDevice? = null
private var captureCallBack: CaptureCallBack? = null
init {
handlerThread.start()
handler = Handler(handlerThread.looper)
cameraManager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
mCameraId = getCamera()[0]
initDefaultPreviewSize()
}
/**
* 获取CameraId
*/
fun getCamera(): Array {
return cameraManager!!.cameraIdList
}
/**
* 获取当前camera的支持预览大小
*/
fun getPreviewSize(): Array? {
var characteristics = cameraManager?.getCameraCharacteristics(mCameraId)
val configs = characteristics?.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
return configs!!.getOutputSizes(SurfaceTexture::class.java)
}
/**
* 根据cameraId打开指定的摄像头
*/
fun openCamera() {
if (ActivityCompat.checkSelfPermission(
context,
Manifest.permission.CAMERA
) != PackageManager.PERMISSION_GRANTED
) {
return
}
createImageReader()
cameraManager?.openCamera(mCameraId, cameraStateCallback, handler)
}
/**
* 打开Camera的状态回调
*/
var cameraStateCallback = object : CameraDevice.StateCallback() {
override fun onOpened(p0: CameraDevice) {
log("cameraStateCallback onOpened")
cameraDevice = p0
createCaptureSession()
}
override fun onDisconnected(p0: CameraDevice) {
log("cameraStateCallback onDisconnected")
}
override fun onError(p0: CameraDevice, p1: Int) {
log("cameraStateCallback onError")
}
}
/**
* 创建CameraCaptureSession的状态回调
*/
var captureSessionCallback = object : CameraCaptureSession.StateCallback() {
override fun onConfigured(p0: CameraCaptureSession) {
captureSession = p0
var captureSessionRequest =
cameraDevice!!.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
captureSessionRequest.addTarget(Surface(captureTexture))
var request = captureSessionRequest.build()
captureSession!!.setRepeatingRequest(request, null, handler)
}
override fun onConfigureFailed(p0: CameraCaptureSession) {
log("captureSessionCallback onConfigureFailed")
}
}
/**
* ImageReader获取到数据时的回调
*/
private var imageAvailableListener = ImageReader.OnImageAvailableListener {
var image = imageReader!!.acquireLatestImage()
var buffer = image.planes[0].buffer
var length = buffer.remaining()
var bytes = ByteArray(length)
buffer.get(bytes)
image.close()
try {
var bmp = BitmapFactory.decodeByteArray(bytes, 0, length, null)
var matrix = Matrix()
matrix.postRotate(90f)
var bitmap = Bitmap.createBitmap(bmp, 0, 0, bmp.width, bmp.height, matrix, false)
if (captureCallBack != null) {
captureCallBack!!.onSucceed(bitmap)
}
} catch (e: Exception) {
if (captureCallBack != null) {
captureCallBack!!.onFailed(Throwable(e.localizedMessage))
}
}
}
/**
* 更改预览,本质上是重新创建CameraCaptureSession和ImageReader
*/
fun changePreviewSize(width: Int, height: Int) {
previewWidth = width
previewHeight = height
if (cameraDevice != null) {
createImageReader()
createCaptureSession()
}
}
/**
* 创建CameraCaptureSession
*/
private fun createCaptureSession() {
captureTexture!!.setDefaultBufferSize(previewWidth, previewHeight);//设置SurfaceTexture缓冲区大小
if (captureSession != null) {
captureSession!!.close()
captureSession = null
}
cameraDevice!!.createCaptureSession(
listOf(
Surface(captureTexture),
(imageReader!!.surface)
), captureSessionCallback, handler
)
}
/**
* 创建ImageReader
*/
private fun createImageReader() {
if (imageReader != null) {
imageReader!!.close()
imageReader = null
}
imageReader = ImageReader.newInstance(previewWidth, previewHeight, ImageFormat.JPEG, 2)
imageReader!!.setOnImageAvailableListener(imageAvailableListener, handler)
}
/**
* 重新打开一个相机
*/
fun openCamera(cameraId: String) {
if (cameraDevice != null) {
releaseCamera()
}
mCameraId = cameraId
initDefaultPreviewSize()
openCamera()
}
/**
* 获取默认的预览尺寸
*/
private fun initDefaultPreviewSize() {
var size = getPreviewSize()!![0]
previewWidth = size.width
previewHeight = size.height
}
/**
* 拍照
*/
fun capturePic(captureCallBack: CaptureCallBack) {
if (cameraDevice == null) {
return
}
this.captureCallBack = captureCallBack
try {
var requestBuilder =
cameraDevice!!.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE)
requestBuilder.addTarget(imageReader!!.surface)
var request = requestBuilder.build()
captureSession!!.capture(request, null, handler)
} catch (e: Exception) {
if (captureCallBack != null) {
captureCallBack.onFailed(Throwable(e.localizedMessage))
}
Toast.makeText(context, "${e.localizedMessage}", Toast.LENGTH_SHORT).show()
}
}
/**
* 释放相机
*/
fun releaseCamera() {
captureSession!!.close()
cameraDevice!!.close()
}
/**
* 释放handlerThread
*/
fun destroy() {
handlerThread.quit()
}
private fun log(string: String) {
Log.d(tag, string)
}
}
预览的使用也很简单,只需要在TextureView创建完成时,调用如下代码
override fun onSurfaceTextureAvailable(p0: SurfaceTexture, p1: Int, p2: Int) {
cameraProvider = CameraProvider(mContext!!,p0)
cameraProvider.openCamera()
}
拍照时这样
cameraProvider.capturePic(object : CaptureCallBack{
override fun onSucceed(bitmap: Bitmap) {
// 处理得到的图片
}
override fun onFailed(e: Throwable) {
}
})
切换摄像头时这样
cameraProvider.openCamera(cameraId)
切换预览分辨率时这样
cameraProvider.changePreviewSize(size.width,size.height)
使用起来还是比较简单的,我写了一个简单的demo,运行效果如下
切换摄像头
哈哈哈哈租的地方有点破旧~不要在意这些细节
快门按钮的实现在这 Android自定义View(14) 《手写一个MIUI的相机快门按钮》
切换分辨率
Github地址
总结
- 注意点
对Camera的大多操作都是耗时的,建议创建一个HandlerThread来统一进行对相机的操作
Camera2的使用也是刚开始学习,如果有误的地方希望大家指出