我们知道 Android 中相机开发是有两套 API 可以使用的,一个是 Camera,这个适用于 Android 5.0 以下,另外一个是 Camera2,这个适用于 Android 5.0 以上。但是这仅仅是系统的建议,其实开发中由于国内厂商对 Camera2 的支持程度各不相同,即便是 5.0 以上的手机,也可能对 Camera2 支持非常差的情况,我们可能还得降级使用 Camera 来开发。
使用 Camera2 开发会涉及到一些系统方法的调用,我们需要大概了解一下他们的作用。
1.相机的管理主要由以下两个类提供:
CameraManager:相机管理类,可以获取相机个数,以及打开或关闭相机等操作。
CameraCharacteristics:获取相机的配置参数,比如获取相机支持的拍摄分辨率大小、ISO范围、曝光时间等,系统提供了大概78个配置选项。
2.相机的预览和拍摄主要由下面的类管理:
CameraDevice:这个相当于是打开相机后当前摄像头的表示,相机开发后会传入一个CameraDevice,我们可以使用此类来创建与相机的连接。
CameraCaputreSession:由CameraDevice配置好后产生的session,用于处理相机预览或者是拍照等处理,就相当于是已经建立连接了,然后现在通过这个CameraCaptureSession处理与相机进行对话。
CaptureRequest:控制本次获取图像的配置,比如配置图片的ISO,对焦方式和曝光时间等。
CaptureResult:描述拍照完成后的结果。
ImageReader:可以用这个类来做简单的捕获图像处理。
3.展示预览图像可以使用 SurfaceView 或 TextureView:
SurfaceView:界面渲染可以放在单独线程,自身不能支持使用动画。
TextureView:只能在拥有硬件加速层层的Window绘制,性能不如SurfaceView,并且可能丢帧,但是可以做一些动画效果。
两者详细差别分析可以参考:https://groups.google.com/a/chromium.org/forum/#!topic/graphics-dev/Z0yE-PWQXc4
Camera2 开发的整个流程就上面介绍的那样:
先请求相机权限
使用 CameraManager找到你想要的摄像头,前置或是后置。 通过 CameraCharacteristics 获取相机的配置信息,方便之后调整相机的各种参数。 通过 CameraManager 打开相机,得到当前的 CameraDevice。 通过 CameraDevice 创建本次会话,得到本次会话的 CameraCaptureSession。 使用 CaptureRequest 设置获取图片的参数信息,设置到 CameraCaptureSession 中。 在 ImageReader 或 CaptureResult 处理得到的图片。 关闭相机(不关闭有可能会导致相机资源占用,导致别的相机无法正常打开)开发之前先来了解一些相机的配置介绍:
AE:Automatic Exposure(自动曝光)。
AF:Auto Focus(自动对焦)。
ISO:International Orization for Standardization,这个是国际标准化组织,由于照相机的感光度最终由这个组织发布,所以称为感光度ISO值。
EV:Exposure value(曝光值)
F:F-number(光圈)
我们通过自己设置这些参数可以拍出非常有意思的照片,下面说一下 Android 为我们提供的与摄像头交互的一些类,以及如何配置自己的相机参数来开发相机。
下面将使用 TextureView 结合 Camera2 API 制作一个简单的相机预览
1. 在TextureView可用状态时打开相机。
//监听view的事件
mTextureView.surfaceTextureListener = this override fun onSurfaceTextureAvailable(surface: SurfaceTexture?, width: Int, height: Int) { //可用的时候会回调当前方法,width是此view的宽,height是高。 openCamera(width,height) } 2.首先在 openCamera 中检查是否有相机权限private fun openCamera(width : Int,height : Int) {
//判断是否有相机权限 if (ContextCompat.checkSelfPermission(activity!!, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { requestCameraPermission() return } //设置要使用的摄像头以及获取摄像头的配置 setUpCameraOutputs(width, height) //打开摄像头 waitOpen() } //申请权限 private fun requestCameraPermission() { requestPermissions(arrayOf(Manifest.permission.CAMERA), REQUEST_CAMERA_PERMISSION) } 3.配置相关属性private fun setUpCameraOutputs(width: Int, height: Int) {
val activity = act val manager = activity.getSystemService(Context.CAMERA_SERVICE) as CameraManager manager.cameraIdList.forEach { cameraId -> val characteristics = manager.getCameraCharacteristics(cameraId) val facing = characteristics.get(CameraCharacteristics.LENS_FACING) //这里我们不使用前置摄像头 if (facing != null && facing == CameraCharacteristics.LENS_FACING_FRONT) { return } //得到相机支持的流配置(包括支持的图片分辨率等),不支持就返回 val map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) ?: return //获取支持的iso范围 val isoRange = characteristics.get(CameraCharacteristics.SENSOR_INFO_SENSITIVITY_RANGE) //得到最高和最低值 if (isoRange != null) { val isoMin = isoRange.lower val isoMax = isoRange.upper } //获取支持的图像曝光时间范围,单位纳秒 val timeRange = characteristics.get(CameraCharacteristics.SENSOR_INFO_EXPOSURE_TIME_RANGE) //得到最大最小值 if (timeRange != null) { val timeMin = timeRange.lower val timeMax = timeRange.upper } //为了方便,这里我们获取支持摄像头支持的最大尺寸来存储 //我们直接使用了 JPEG 的图片格式,android 支持的图片格式可以查看ImageFormat这个类 val largest = Collections.max( Arrays.asList(*map.getOutputSizes(ImageFormat.JPEG)), CompareSizesByArea()) //创建一个用于获取摄像头图片的 ImageReader,最多接受 2 个 mImageReader = ImageReader.newInstance(largest.width, largest.height, ImageFormat.JPEG, 2) //监听接受到图片的事件 mImageReader?.setOnImageAvailableListener(mOnImageAvailableListener, mHandler) //检查是否支持 flash mFlashSupported = characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE) ?: false //得到当前的摄像头id mCameraId = cameraId } } 4.根据 id 打开相机private fun waitOpen() {
val activity = act val manager = activity.getSystemService(Context.CAMERA_SERVICE) as CameraManager if (mCameraId == null){ //没有找到符合的摄像头 return } manager.openCamera(mCameraId,mStateCallback,mHandler) } //在这里得到打开相机的各种回调 private val mStateCallback : CameraDevice.StateCallback = object : CameraDevice.StateCallback(){ override fun onOpened(camera: CameraDevice) { //得到当前摄像头 mCameraDevice = camera //创建预览请求 createCameraPreviewSession() } override fun onDisconnected(camera: CameraDevice) { } override fun onError(camera: CameraDevice, error: Int) { } } 5.创建预览请求private fun createCameraPreviewSession(){
//获取view的surface,这里的宽高应该是获取适合摄像头当前的宽高,这里为了方便就直接使用屏幕宽高了 val texture = mTextureView.surfaceTexture texture.setDefaultBufferSize(act.getWidth(),act.getHeight()) val surface = Surface(texture) //构建预览请求 mPreviewRequestBuilder = mCameraDevice?.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW) mPreviewRequestBuilder?.addTarget(mImageReader?.surface) mPreviewRequestBuilder?.addTarget(surface) //创建预览会话 mCameraDevice?.createCaptureSession(listOf(surface, mImageReader?.surface),mSessionCallBack,null) } //会在mSessionCallBack得到我们本次的session private val mSessionCallBack : CameraCaptureSession.StateCallback = object : CameraCaptureSession.StateCallback(){ override fun onConfigureFailed(session: CameraCaptureSession) {} override fun onConfigured(session: CameraCaptureSession) { if (mCameraDevice == null){ return } //得到本次的会话类 mCaptureSession= session //设置为自动对焦的会话预览 mPreviewRequestBuilder?.set(CaptureRequest.CONTROL_AF_MODE,CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE) //将imageReader添加进来即可在此类中得到预览的图片 mPreviewRequestBuilder?.addTarget(mImageReader?.surface) mPreviewRequest = mPreviewRequestBuilder?.build() //设置为连续请求 mCaptureSession?.setRepeatingRequest(mPreviewRequest,mCaptureCallback,mHandler) } } 这里还可以设置别的模式,比如设置可以自己调节ISO大小,值的大小可以根据从相机配置读取的参数范围设置/**
* 设置ios感光度以及曝光时间 * ae 自动曝光 Automatic Exposure * af 自动对焦 Auto Focus * iso 灵敏度 * exposure 曝光时间 * frame 帧持续时间 */ private fun setIsoMode(iso: Int, exposure: Long, frame: Long) { //禁用所有自动设置 // mPreviewRequestBuilder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_OFF); //只是禁用曝光,白平衡继续开启,自己设置iso等值,必须禁用曝光 mPreviewRequestBuilder?.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_OFF) mPreviewRequestBuilder?.set(CaptureRequest.SENSOR_EXPOSURE_TIME, exposure) mPreviewRequestBuilder?.set(CaptureRequest.SENSOR_SENSITIVITY, iso) mPreviewRequestBuilder?.set(CaptureRequest.SENSOR_FRAME_DURATION, frame) mPreviewRequestBuilder?.addTarget(mImageReader?.surface) //需要预览的话改成这个并且添加到下面的createCaptureSession里 mPreviewRequest = mPreviewRequestBuilder?.build() mCaptureSession?.setRepeatingRequest(mPreviewRequest, mCaptureCallback, mHandler) } 6.在 ImageReader 中处理图片结果//这个是刚刚为mImageReader添加的回调接口
private val mOnImageAvailableListener = (ImageReader.OnImageAvailableListener { reader -> //获取下一张图片 val image = reader.acquireNextImage() //这里的planes大小和所选的图片格式有关,比如YUV_444就有三个通道 val buffer = image.planes[0].buffer val bytes = ByteArray(buffer.remaining()) val bitmap = BitmapFactory.decodeByteArray(bytes,0,bytes.size) //使用完记得回收 image.close() }) 7.操作完之后就记得释放相机资源private fun closeCamera(){
if (mCaptureSession != null) { mCaptureSession?.close() mCaptureSession = null } if (mCameraDevice != null) { mCameraDevice?.close() mCameraDevice = null } if (mImageReader != null) { mImageReader?.close() mImageReader = null } }