Android Camera2 開發實踐指南
我們知道 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@forEach
}
//得到相機支援的流配置(包括支援的圖片解析度等),不支援就返回
val map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
?: return@forEach
//獲取支援的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
* @param iso 靈敏度
* @param exposure 曝光時間
* @param 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
}
}<