Camera2
参考官方例子
流程图
这里引用了管道的概念将安卓设备和摄像头之间联通起来,系统向摄像头发送 Capture 请求,而摄像头会返回 CameraMetadata。这一切建立在一个叫作 CameraCaptureSession 的会话中。
类图
CameraManager
摄像头管理器。这是一个全新的系统管理器,专门用于检测系统摄像头、打开系统摄像头。另外,调用CameraManager
的getCameraCharacteristics(String cameraId)
方法即可获取指定摄像头的相关特性。
CameraCharacteristics
摄像头特性。该对象通过CameraManager
来获取,用于描述特定摄像头所支持的各种特性。类似与原来的CameraInfo
。
CameraDevice
代表系统摄像头。该类的功能类似于早期的Camera类。而每个 CameraDevice
自己会负责建立 CameraCaptureSession
以及建立 CaptureRequest
。
CameraCaptureSession
这是一个非常重要的API,当程序需要预览、拍照时,都需要先通过该类的实例创建Session
。而且不管预览还是拍照,也都是由该对象的方法进行控制的,其中控制预览的方法为setRepeatingRequest()
;控制拍照的方法为capture()
。
为了监听CameraCaptureSession
的创建过程,以及监听CameraCaptureSession
的拍照过程,Camera v2 API为CameraCaptureSession
提供了StateCallback
、CaptureCallback
等内部类。
CameraRequest和CameraRequest.Builder
当程序调用setRepeatingRequest()
方法进行预览时,或调用capture()
方法进行拍照时,都需要传入CameraRequest
参数。CameraRequest
代表了一次捕获请求,用于描述捕获图片的各种参数设置,比如对焦模式、曝光模式……总之,程序需要对照片所做的各种控制,都通过CameraRequest
参数进行设置。CameraRequest.Builder
则负责生成CameraRequest
对象
预览流程
权限与请求
1 2 3 4 5 6 |
<uses-permission android:name="android.permission.CAMERA" /> <uses-feature android:name="android.hardware.camera" /> <uses-feature android:name="android.hardware.camera.autofocus" /> 6.0以上需要动态获取 requestPermissions(arrayOf(Manifest.permission.CAMERA), REQUEST_CAMERA_PERMISSION) |
开启线程
可以自定义,也可以使用HandlerThread,之后传递相关callback的回调使用这个线程中的handler
1 2 3 4 5 |
backgroundThread = HandlerThread("CameraBackground") .apply { } .also { it.start() } backgroundHandler = Handler(backgroundThread?.looper) |
### 获取CameraManager
1
|
val manager = activity!!.getSystemService(Context.CAMERA_SERVICE) as CameraManager |
### 获取CameraCharacteristics
根据获取CameraManager
中的cameraIdList
获取
1 2 3 4 |
for (cameraId in manager.cameraIdList) { // 获取摄像头特性 val characteristics = manager.getCameraCharacteristics(cameraId) 、、、 |
### 设置输出尺寸大小
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
private fun chooseOptimalSize(choices: Array<Size>, textureViewWidth: Int, textureViewHeight: Int, maxWidth: Int, maxHeight: Int, aspectRatio: Size): Size { val bigEnoungh = ArrayList<Size>() val notBigEnough = ArrayList<Size>() val w = aspectRatio.width val h = aspectRatio.height // 找到最适合的分辨率 for (option in choices) { // 如果满足输出的尺寸小于等于最大尺寸,并且宽高比等于aspectRatio的宽高比 if (option.width <= maxWidth && option.height <= maxHeight && option.height == option.width * h / w) { if (option.width >= textureViewWidth && option.height >= textureViewHeight) { // option的比view较大 bigEnoungh.add(option) } else { // option的比view尺寸小 notBigEnough.add(option) } } } return when { // 优先bigEnoungh的最小尺寸 , 目的是为了接近view的尺寸 bigEnoungh.size > 0 -> Collections.min(bigEnoungh, CompareSizesByArea()) // 然后输出notBigEnough的最大尺寸,目的是为了接近view的尺寸 notBigEnough.size > 0 -> Collections.max(notBigEnough, CompareSizesByArea()) else -> { Log.e(TAG, "Couldn't find any suitable preview size") choices[0] } } } |
### 打开摄像头
这里有个CameraDevice.StateCallback(),以及handler回调
1
|
manager.openCamera(cameraId, stateCallback, backgroundHandler) |
CameraDevice.StateCallback()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
private val stateCallback = object : CameraDevice.StateCallback() { override fun onOpened(cameraDevice: CameraDevice) { cameraOpenCloseLock.release() this@Camera2BasicFragment.cameraDevice = cameraDevice // 如果打开了,则创建session createCameraPreviewSession() } override fun onDisconnected(cameraDevice: CameraDevice) { cameraOpenCloseLock.release() cameraDevice.close() this@Camera2BasicFragment.cameraDevice = null } override fun onError(cameraDevice: CameraDevice, error: Int) { onDisconnected(cameraDevice) this@Camera2BasicFragment.activity?.finish() } |
### 创建Session
先创建CaptureRequestBuilder,并且绑定Surface,然后调用createCaptureSession方法 CameraCaptureSession.StateCallback(),最后调用setRepeatingRequest
方法传入CaptureRequest,handler,CameraCaptureSession.CaptureCallback()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
private fun createCameraPreviewSession() { try { // 初始化SurfaceTexture val texture = textureView.surfaceTexture // 设置高宽 texture.setDefaultBufferSize(previewSize.width, previewSize.height) // 初始化Surface val surfece = Surface(texture) // 创建PREVIEW的CaptureRequestBuilder previewRequestBuilder = cameraDevice!!.createCaptureRequest( CameraDevice.TEMPLATE_PREVIEW ) // 添加目标 previewRequestBuilder.addTarget(surfece) // createSession cameraDevice?.createCaptureSession(Arrays.asList(surfece, imageReader?.surface), object : CameraCaptureSession.StateCallback() { override fun onConfigureFailed(session: CameraCaptureSession?) { activity!!.showToast("Failed") } override fun onConfigured(session: CameraCaptureSession?) { cameraDevice ?: return captureSession = session try { // 预览时自动对焦 previewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE) // 是否设置自动闪光 setAutoFlash(previewRequestBuilder) // 预览view previewRequest = previewRequestBuilder.build() // 该方法注意CameraCaptureSession.CaptureCallback() captureSession?.setRepeatingRequest(previewRequest, captureCallback, backgroundHandler) } catch (e: CameraAccessException) { Log.e(TAG, e.toString()) } } }, null) } catch (e: CameraAccessException) { Log.e(TAG, e.toString()) } } |
CameraCaptureSession.CaptureCallback()
这个是设置预览时的回调
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
// 在图像捕捉部分向前进度时调用; 一些(但不是全部)来自图像捕获的结果是可用的 override fun onCaptureProgressed(session: CameraCaptureSession, request: CaptureRequest, partialResult: CaptureResult) { } // 当图像捕捉已完全完成并且所有结果元数据可用时调用此方法 override fun onCaptureCompleted(session: CameraCaptureSession, request: CaptureRequest, result: TotalCaptureResult) { } // 当捕获序列在通过此侦听器返回之前CaptureResult 或之后中止时 override fun onCaptureSequenceAborted(session: CameraCaptureSession?, sequenceId: Int) { super.onCaptureSequenceAborted(session, sequenceId) } // 此回调将在帧的捕捉失败时立即调用 override fun onCaptureFailed(session: CameraCaptureSession?, request: CaptureRequest?, failure: CaptureFailure?) { } // 捕获序列完成并且所有CaptureResult 或CaptureFailure通过此侦听器返回时 override fun onCaptureSequenceCompleted(session: CameraCaptureSession?, sequenceId: Int, frameNumber: Long) { } // 此回调将在帧的捕捉开始时立即调用 override fun onCaptureStarted(session: CameraCaptureSession?, request: CaptureRequest?, timestamp: Long, frameNumber: Long) { } //如果捕获的单个缓冲区无法发送到目标表面,则调用此方法。 override fun onCaptureBufferLost(session: CameraCaptureSession?, request: CaptureRequest?, target: Surface?, frameNumber: Long) { } |
## 拍照流程
### 锁定焦点并提交图像请求
1 2 3 4 5 6 7 |
// 设置自动对焦模式为自动对焦 previewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_START) state = STATE_WAITING_LOCK // 提交要由相机设备拍摄的图像的请求 captureSession?.capture(previewRequestBuilder.build(), captureCallback, backgroundHandler) |
进行拍照
进行拍照前,还有一些AF,AE对应模式状态的处理,最后调用该方法用imageReader进行拍照保存图片
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
private fun captureStillPicture() { try { if (activity == null || cameraDevice == null) return val rotation = activity!!.windowManager.defaultDisplay.rotation // CameraDevice.TEMPLATE_STILL_CAPTURE 创建适合静态图像捕获的请求。具体来说,这意味着优先考虑帧速率的图像质量 // 把imageReader添加进去 val captureBuilder = cameraDevice?.createCaptureRequest( CameraDevice.TEMPLATE_STILL_CAPTURE)?.apply { addTarget(imageReader?.surface) // 控制jpeg的方向 set(CaptureRequest.JPEG_ORIENTATION, (ORIENTATIONS.get(rotation) + sensorOrientation + 270) % 360) // CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE 在这种模式下,AF算法会不断修改镜头位置,以尝试提供持续聚焦的图像流 set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE) }?.also { setAutoFlash(it) } val captureCallback = object : CameraCaptureSession.CaptureCallback() { override fun onCaptureCompleted(session: CameraCaptureSession?, request: CaptureRequest?, result: TotalCaptureResult?) { activity?.showToast("Saved: $file") Log.d(TAG, file.toString()) // 成功后取消锁定自动对焦 unlockFocus() } } captureSession?.apply { stopRepeating() abortCaptures() capture(captureBuilder?.build(), captureCallback, null) } } catch (e: CameraAccessException) { Log.e(TAG, e.toString()) } } |
### ImageReader
1 2 3 4 5 6 7 8 9 10 |
// 读取信息Reader imageReader = ImageReader.newInstance(largest.width, largest.height, ImageFormat.JPEG, 2).apply { setOnImageAvailableListener(onImageAvailableListener, backgroundHandler) } private val onImageAvailableListener = ImageReader.OnImageAvailableListener { backgroundHandler?.post(ImageSaver(it.acquireLatestImage(), file)) } |
## 录制流程
录制使用MediaRecorder进行录制
### 设置MediaRecorder
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
// 设置方向 val rotation = cameraActivity.windowManager.defaultDisplay.rotation when (sensorOrientation) { SENSOR_ORIENTATION_DEFAULT_DEGREES -> mediaRecorder?.setOrientationHint(DEFAULT_ORIENTATIONS.get(rotation)) SENSOR_ORIENTATION_INVERSE_DEGREES -> mediaRecorder?.setOrientationHint(INVERSE_ORIENTATIONS.get(rotation)) } mediaRecorder?.apply { // 设置音频来源为MIC setAudioSource(MediaRecorder.AudioSource.MIC) // 设置视频来源为SURFACE setVideoSource(MediaRecorder.VideoSource.SURFACE) // 设置输出格式为OutputFormat.MPEG_4 setOutputFormat(MediaRecorder.OutputFormat.MPEG_4) // 设置输出文件夹 setOutputFile(nextVideoAbsolutePath) // 设置码率 setVideoEncodingBitRate(10000000) // 设置帧率 setVideoFrameRate(30) // 设置大小 setVideoSize(videoSize.width, videoSize.height) // 设置编码器为H264 setVideoEncoder(MediaRecorder.VideoEncoder.H264) // 设置音频编码器为ACC setAudioEncoder(MediaRecorder.AudioEncoder.AAC) prepare() } |
### 启用录制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
closePreviewSession() setUpMediaRecorder() val texture = textureView.surfaceTexture.apply { setDefaultBufferSize(previewSize.width, previewSize.height) } // Set up Surface for camera preview and MediaRecorder val previewSurface = Surface(texture) val recorderSurface = mediaRecorder!!.surface val surfaces = ArrayList<Surface>().apply { add(previewSurface) add(recorderSurface) } previewRequestBuilder = cameraDevice!!.createCaptureRequest(TEMPLATE_RECORD).apply { addTarget(previewSurface) addTarget(recorderSurface) } // Start a capture session // Once the session starts, we can update the UI and start recording cameraDevice?.createCaptureSession(surfaces, object : CameraCaptureSession.StateCallback() { override fun onConfigured(cameraCaptureSession: CameraCaptureSession) { captureSession = cameraCaptureSession updatePreview() activity?.runOnUiThread { videoButton.setText(R.string.stop) isRecordingVideo = true mediaRecorder?.start() } } override fun onConfigureFailed(cameraCaptureSession: CameraCaptureSession) { if (activity != null) showToast("Failed") } }, backgroundHandler) |