一、简述
1、camera2的发展是由于手机厂商在添加过多的摄像头,而google的camera1只是针对单摄像头的所以对于多摄像头的处理不是很友好。
2、由于手机主板需要占据手机大部分的空间,而手机的摄像头也是长方形的,所以手机厂商的后置摄像头的放置一般都是逆时针旋转90度的。
二、学习camera2需要了解哪些api的使用?
CameraManager//获取相机的一些参数信息和打开相机的管理类
CameraCaptureSession//用来向相机设备发送获取图像的请求
ImageReader//获取屏幕渲染数据 可以搭配MediaProjectionManager录屏一起使用
三、学习camera2遇到的一些问题
1:用户在使用手机在拍照的时候不会是完全竖直使用的,那在用户点击拍照的时候怎么处理手机旋转带来的问题?
2:前置摄像头就像是镜子,所以拍照出来的数据是镜像的我们需要怎么处理?
3:用户切换前后摄像头代码应该怎么做?
带着以上3个问题开始代码之旅
四、问题处理
- 手机旋转我们可以做监听处理,系统提供了OrientationEventListener类
val mOrEventListener: OrientationEventListener =
object : OrientationEventListener(context) {
override fun onOrientationChanged(orientation: Int) {
if (((orientation >= 0) && (orientation <= 45)) || (orientation > 315) && (orientation <= 360)) {
phoneDegree = 0;
} else if ((orientation > 45) && (orientation <= 135)) {
phoneDegree = 90;
} else if ((orientation > 135) && (orientation <= 225)) {
phoneDegree = 180;
} else if ((orientation > 225) && (orientation <= 315)) {
phoneDegree = 270;
}
}
}
mOrEventListener.enable()
我们将获取的旋转角度保存为全局变量,此时只是获取了旋转角度,还有摄像头对应得角度我们还需要再处理,上代码
val cameraCharacteristics = cameraManager!!.getCameraCharacteristics(mCameraId)
var mOrientation = cameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION)
mCameraId是对应得前置还是后置的摄像头,mOrientation 就是相机需要的旋转角度
- 对应得前置摄像头的处理方案
(mOrientation-phoneDegree + 360)% 360
- 对应得后置摄像头的处理方案
(mOrientation+phoneDegree )% 360
至此第一个问题可以解决了!!!
- 镜像处理应该大家都做过很简单的一个沿着x翻转就可以了,这里我们用的是Matrix
matrix.postScale(-1F, 1F);//利用matrix 对矩阵进行转换,x轴镜像
至此第二个问题可以解决了!!!
- 第三个不是技术问题,是摄像头在使用过程中不支持动态更改配置,如果需要更改的话必须重新初始化所有配置操作。所以封装好代码按步骤初始化吧。这类需要提的几点就是,对于camera如果已经打开过了我们需要release
captureSession!!.close()
cameraDevice!!.close()
对于ImageReader类我们已经创建过的需要close
imageReader!!.close()
至此以上遇到的一些问题都解决了!!!没有可以难倒我们的了。接下来就是看看代码如何处理吧
(代码主要是kotlin写的,初学者,希望写的不对的大家可以指教!!!!)
下面贴一下完整的代码。
class Camera2Manager(var context: Context, var captureTexture: SurfaceTexture) {
private val tag = Camera2Manager::class.java.simpleName
private var handlerThread = HandlerThread("Camera2Manager")
private var handler: Handler? = null
private var mCameraId = "0"//初始为后置摄像头
private var cameraManager: CameraManager? = null//获取相机的一些参数信息和打开相机的管理类
private var captureSession: CameraCaptureSession? = null//用来向相机设备发送获取图像的请求
private var imageReader: ImageReader? = null//获取屏幕渲染数据 可以搭配MediaProjectionManager录屏一起使用
private var previewWidth = 0//图片预览宽
private var previewHeight = 0//图片预览高
private var cameraDevice: CameraDevice? = null
private var captureCallBack: CaptureCallBack? = null
private var isFrontCamera: Boolean = false//前置摄像头的判断
private var phoneDegree = 0;//手机的旋转角度
init {
handlerThread.start()//启动工作线程
handler=Handler(handlerThread.looper)
cameraManager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
mCameraId = getCamera()[0]
initDefaultPreviewSize()
startOrientationListener()
}
/**
* 主要是监听手机旋转角度来保证所拍摄的照片始终是正向的
*/
private fun startOrientationListener() {
val mOrEventListener: OrientationEventListener =
object : OrientationEventListener(context) {
override fun onOrientationChanged(orientation: Int) {
if (((orientation >= 0) && (orientation <= 45)) || (orientation > 315) && (orientation <= 360)) {
phoneDegree = 0;
} else if ((orientation > 45) && (orientation <= 135)) {
phoneDegree = 90;
} else if ((orientation > 135) && (orientation <= 225)) {
phoneDegree = 180;
} else if ((orientation > 225) && (orientation <= 315)) {
phoneDegree = 270;
}
}
}
mOrEventListener.enable()
}
/**
* 获取CameraId 一台设备会有多个摄像头
*/
private fun getCamera(): Array<out String> {
return cameraManager!!.cameraIdList
}
/**
* 获取当前camera的支持预览大小 可以供用户选择分辨率不过一般给与最大分辨率拍摄
*/
private fun getPreviewSize(): Array<out Size>? {
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的状态回调
*/
private var cameraStateCallback = object : CameraDevice.StateCallback() {
override fun onOpened(device: CameraDevice) {
cameraDevice = device
createCaptureSession()
}
override fun onDisconnected(device: CameraDevice) {
}
override fun onError(device: CameraDevice, p1: Int) {
}
}
/**
* 创建CameraCaptureSession的状态回调
*/
private var captureSessionCallback = object : CameraCaptureSession.StateCallback() {
override fun onConfigured(session: CameraCaptureSession) {
captureSession = session
var captureSessionRequest =
cameraDevice!!.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
captureSessionRequest.addTarget(Surface(captureTexture))
// 设置自动对焦模式
captureSessionRequest?.set(
CaptureRequest.CONTROL_AF_MODE,
CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
// 设置自动曝光模式
captureSessionRequest?.set(CaptureRequest.CONTROL_AE_MODE,
CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
var request = captureSessionRequest.build()
captureSession!!.setRepeatingRequest(request, null, handler)
}
override fun onConfigureFailed(session: CameraCaptureSession) {
}
}
/**
* 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()
val cameraCharacteristics = cameraManager!!.getCameraCharacteristics(mCameraId)
var mOrientation = cameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION)
if (isFrontCamera) {
matrix.postRotate((mOrientation-phoneDegree + 360)% 360 * 1.0F)
matrix.postScale(-1F, 1F);//利用matrix 对矩阵进行转换,x轴镜像
} else {
matrix.postRotate((mOrientation+phoneDegree )% 360 * 1.0F)
}
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()
}
//可以根据下面的代码判断是否是前置摄像头或者后置
// val characteristics = cameraManager!!.getCameraCharacteristics(mCameraId)
// if(characteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT)//前置
// if(characteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_BACK)//后置
mCameraId = cameraId
isFrontCamera = when (cameraId) {
"0" -> false
else -> true
}
initDefaultPreviewSize()
openCamera()
}
/**
* 获取默认的预览尺寸 取第一个尺寸的默认最大
*/
private fun initDefaultPreviewSize() {
var previewSize = getPreviewSize()
for (i in 0 until previewSize?.size!!) {
var width = previewSize[i].width
var height = previewSize[i].height
Log.e("我的预览尺寸$i", "$width ---- $height")
}
var size = previewSize!![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))
}
}
}
/**
* 释放相机
*/
fun releaseCamera() {
captureSession!!.close()
cameraDevice!!.close()
}
/**
* 释放handlerThread
*/
fun destroy() {
handlerThread.quit()
}
}
至此暂时结束简单的处理,接下来有时间会写更详细的处理,还有camerax的使用之类的。
简书不一定会常用,以后也会不定期更新一些关于音视频方面的东西,希望跟志同道合的朋友一起学习进步。
下一篇会写android端p2p的投屏软件,喜欢的朋友可以关注下一起进步。
追加一些自动对焦的处理,引用下别人的文章Android Camera2之 手动点击区域对焦