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) |