
Android自定義Camera2相機(jī)
寫在前面
Google從Android 5.0 L(API 21) 版本,開始引入Camera2(android.hardware.camera2)以取代Camera1(android.hardware.Camera)相機(jī)框架。
Camera2相比于之前的Camera1架構(gòu)完全不同,使用起來比較復(fù)雜,與此同時(shí)功能也變得非常強(qiáng)大。
此篇博客,能夠幫助你快速構(gòu)建并理解自定義Camera2相機(jī)的關(guān)鍵步驟。
完整代碼,請(qǐng)移步:https://github.com/zhijunhong/custom_camera/tree/master/camera2
使用Camera2的優(yōu)點(diǎn)
通過設(shè)計(jì)框架的改造和優(yōu)化,Camera2具備了以下優(yōu)點(diǎn):
- 改進(jìn)了新硬件的性能,使用更先進(jìn)的API架構(gòu);
- 可以獲取更多的幀(預(yù)覽/拍照)信息以及手動(dòng)控制每一幀的參數(shù);
- 對(duì)Camera的控制更加完全(比如支持調(diào)整focus distance, 剪裁預(yù)覽/拍照?qǐng)D片);
- 支持更多圖片格式(yuv/raw)以及高速連拍;
- ......
自定義Camera2相機(jī)
一些概念
1. Pipeline
Camera2的API模型被設(shè)計(jì)成一個(gè) Pipeline(管道),它按順序處理每一幀的請(qǐng)求并返回請(qǐng)求結(jié)果給客戶端。下面這張來自官方的圖展示了Pipeline的工作流程,我們會(huì)通過一個(gè)簡單的例子詳細(xì)解釋這張圖。

Pipeline示意圖
為了解釋上面的示意圖,假設(shè)我們想要同時(shí)拍攝兩張不同尺寸的圖片,并且在拍攝的過程中閃光燈必須亮起來。整個(gè)拍攝流程如下:
- 創(chuàng)建一個(gè)用于從Pipeline獲取圖片的CaptureRequest;
- 修改CaptureRequest的閃光燈配置,讓閃光燈在拍照過程中亮起來;
- 創(chuàng)建兩個(gè)不同尺寸的Surface用于接收?qǐng)D片數(shù)據(jù),并且將它們添加到CaptureRequest中;
- 發(fā)送配置好的CaptureRequest到Pipeline中等待它返回拍照結(jié)果。
一個(gè)新的CaptureRequest會(huì)被放入一個(gè)被稱作Pending Request Queue的隊(duì)列中等待被執(zhí)行,當(dāng)In-Flight Capture Queue隊(duì)列空閑的時(shí)候就會(huì)從Pending Request Queue獲取若干個(gè)待處理的CaptureRequest,并且根據(jù)每一個(gè)CaptureRequest 的配置進(jìn)行Capture操作。最后我們從不同尺寸的Surface中獲取圖片數(shù)據(jù)并且還會(huì)得到一個(gè)包含了很多與本次拍照相關(guān)的信息的CaptureResult,流程結(jié)束。
2. Supported Hardware Level
相機(jī)功能的強(qiáng)大與否和硬件息息相關(guān),不同廠商對(duì) Camera2 的支持程度也不同,所以Camera2定義了一個(gè)叫做Supported Hardware Level的重要概念。其作用是將不同設(shè)備上的Camera2根據(jù)功能的支持情況劃分成多個(gè)不同級(jí)別以便開發(fā)者能夠大概了解當(dāng)前設(shè)備上Camera2的支持情況。截止到Android P為止,從低到高一共有LEGACY、LIMITED、FULL 和 LEVEL_3四個(gè)級(jí)別:
- LEGACY:向后兼容的級(jí)別,處于該級(jí)別的設(shè)備意味著它只支持Camera1的功能,不具備任何Camera2高級(jí)特性;
- LIMITED:除了支持Camera1的基礎(chǔ)功能之外,還支持部分Camera2高級(jí)特性的級(jí)別;
- FULL:支持所有Camera2的高級(jí)特性;
- LEVEL_3:新增更多Camera2高級(jí)特性,例如YUV數(shù)據(jù)的后處理等。
3. Capture
相機(jī)的所有操作和參數(shù)配置最終都是服務(wù)于圖像捕獲,例如對(duì)焦是為了讓某一個(gè)區(qū)域的圖像更加清晰,調(diào)節(jié)曝光補(bǔ)償是為了調(diào)節(jié)圖像的亮度等。因此,在Camera2 里面所有的相機(jī)操作和參數(shù)配置都被抽象成Capture(捕獲),所以不要簡單的把Capture直接理解成是拍照,因?yàn)镃apture操作可能僅僅是為了讓預(yù)覽畫面更清晰而進(jìn)行對(duì)焦而已。如果你熟悉Camera,那你可能會(huì)問 setFlashMode() 在哪?setFocusMode() 在哪?takePicture() 在哪?告訴你,它們都是通過Capture 來實(shí)現(xiàn)的。
Capture從執(zhí)行方式上又被細(xì)分為【單次模式】、【多次模式】和【重復(fù)模式】三種,我們來一一解釋下:
- 單次模式(One-shot):指的是只執(zhí)行一次的Capture操作,例如設(shè)置閃光燈模式、對(duì)焦模式和拍一張照片等。多個(gè)一次性模式的Capture會(huì)進(jìn)入隊(duì)列按順序執(zhí)行。
- 多次模式(Burst):指的是連續(xù)多次執(zhí)行指定的Capture操作,該模式和多次執(zhí)行單次模式的最大區(qū)別是連續(xù)多次Capture期間不允許插入其他任何Capture 操作,例如連續(xù)拍攝100張照片,在拍攝這100張照片期間任何新的Capture請(qǐng)求都會(huì)排隊(duì)等待,直到拍完100張照片。多組多次模式的Capture會(huì)進(jìn)入隊(duì)列按順序執(zhí)行。
- 重復(fù)模式(Repeating):指的是不斷重復(fù)執(zhí)行指定的Capture操作,當(dāng)有其他模式的Capture提交時(shí)會(huì)暫停該模式,轉(zhuǎn)而執(zhí)行其他被模式的Capture,當(dāng)其他模式的 Capture 執(zhí)行完畢后又會(huì)自動(dòng)恢復(fù)繼續(xù)執(zhí)行該模式的Capture,例如顯示預(yù)覽畫面就是不斷 Capture 獲取每一幀畫面。該模式的 Capture 是全局唯一的,也就是新提交的重復(fù)模式Capture會(huì)覆蓋舊的重復(fù)模式Capture。
關(guān)鍵API
|
CameraManager ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? |
CameraManager是一個(gè)負(fù)責(zé)查詢和建立相機(jī)連接的系統(tǒng)服務(wù),它的功能不多,這里列出幾個(gè)CameraManager的關(guān)鍵功能: 1.將相機(jī)信息封裝到CameraCharacteristics中,并提獲取CameraCharacteristics實(shí)例的方式; 2.根據(jù)指定的相機(jī)ID連接相機(jī)設(shè)備; 3.提供將閃光燈設(shè)置成手電筒模式的快捷方式。 |
|---|---|
|
CameraCharacteristics ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? |
CameraCharacteristics 是一個(gè)只讀的相機(jī)信息提供者,其內(nèi)部攜帶大量的相機(jī)信息,包括代表相機(jī)朝向的 LENS_FACING;判斷閃光燈是否可用的 FLASH_INFO_AVAILABLE;獲取所有可用 AE 模式的 CONTROL_AE_AVAILABLE_MODES 等。如果你對(duì)Camera1比較熟悉,那么CameraCharacteristics有點(diǎn)像Camera1的 Camera.CameraInfo 或者 Camera.Parameters。 |
|
CameraDevice ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? |
CameraDevice 代表當(dāng)前連接的相機(jī)設(shè)備,它的職責(zé)有以下四個(gè): 1.根據(jù)指定的參數(shù)創(chuàng)建 CameraCaptureSession; 2.根據(jù)指定的模板創(chuàng)建 CaptureRequest; 3.關(guān)閉相機(jī)設(shè)備; 4.監(jiān)聽相機(jī)設(shè)備的狀態(tài),例如斷開連接、開啟成功和開啟失敗等。 熟悉Camera1的人可能會(huì)說CameraDevice就是Camera1的 Camera 類,實(shí)則不是,Camera 類幾乎負(fù)責(zé)了所有相機(jī)的操作,而 CameraDevice 的功能則十分的單一,就是只負(fù)責(zé)建立相機(jī)連接的事務(wù),而更加細(xì)化的相機(jī)操作則交給了稍后會(huì)介紹的CameraCaptureSession。 |
|
Surface ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? |
Surface 是一塊用于填充圖像數(shù)據(jù)的內(nèi)存空間,例如你可以使用 SurfaceView 的 Surface 接收每一幀預(yù)覽數(shù)據(jù)用于顯示預(yù)覽畫面,也可以使用 ImageReader 的 Surface 接收 JPEG 或 YUV 數(shù)據(jù)。每一個(gè) Surface 都可以有自己的尺寸和數(shù)據(jù)格式,你可以從 CameraCharacteristics 獲取某一個(gè)數(shù)據(jù)格式支持的尺寸列表。 |
|
CameraCaptureSession ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? |
CameraCaptureSession 實(shí)際上就是配置了目標(biāo) Surface 的 Pipeline 實(shí)例,我們?cè)谑褂孟鄼C(jī)功能之前必須先創(chuàng)建 CameraCaptureSession 實(shí)例。一個(gè) CameraDevice 一次只能開啟一個(gè) CameraCaptureSession,絕大部分的相機(jī)操作都是通過向 CameraCaptureSession 提交一個(gè) Capture 請(qǐng)求實(shí)現(xiàn)的,例如拍照、連拍、設(shè)置閃光燈模式、觸摸對(duì)焦、顯示預(yù)覽畫面等。 |
|
CaptureRequest ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? |
CaptureRequest 是向 CameraCaptureSession 提交 Capture 請(qǐng)求時(shí)的信息載體,其內(nèi)部包括了本次 Capture 的參數(shù)配置和接收?qǐng)D像數(shù)據(jù)的 Surface。CaptureRequest 可以配置的信息非常多,包括圖像格式、圖像分辨率、傳感器控制、閃光燈控制、3A 控制等,可以說絕大部分的相機(jī)參數(shù)都是通過 CaptureRequest 配置的。值得注意的是每一個(gè) CaptureRequest 表示一幀畫面的操作,這意味著你可以精確控制每一幀的 Capture 操作。 |
|
CaptureResult ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? |
CaptureResult 是每一次 Capture 操作的結(jié)果,里面包括了很多狀態(tài)信息,包括閃光燈狀態(tài)、對(duì)焦?fàn)顟B(tài)、時(shí)間戳等等。例如你可以在拍照完成的時(shí)候,通過 CaptureResult 獲取本次拍照時(shí)的對(duì)焦?fàn)顟B(tài)和時(shí)間戳。需要注意的是,CaptureResult 并不包含任何圖像數(shù)據(jù),前面我們?cè)诮榻B Surface 的時(shí)候說了,圖像數(shù)據(jù)都是從 Surface 獲取的。 |
|
ImageReader ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? |
用于從相機(jī)打開的通道中讀取需要的格式的原始圖像數(shù)據(jù),可以設(shè)置多個(gè)ImageReader。 |
開發(fā)流程

1.獲取CameraManager
private val cameraManager: CameraManager by lazy { getSystemService(CameraManager::class.java) }
2.獲取相機(jī)信息
val cameraIdList = cameraManager.cameraIdList
cameraIdList.forEach { cameraId ->
val cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraId)
if (cameraCharacteristics.isHardwareLevelSupported(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL)) {
if (cameraCharacteristics[CameraCharacteristics.LENS_FACING] == CameraCharacteristics.LENS_FACING_FRONT) {
frontCameraId = cameraId
frontCameraCharacteristics = cameraCharacteristics
} else if (cameraCharacteristics[CameraCharacteristics.LENS_FACING] == CameraCharacteristics.LENS_FACING_BACK) {
backCameraId = cameraId
backCameraCharacteristics = cameraCharacteristics
}
}
}
通過CameraManager獲取到所有攝像頭cameraId,通過循環(huán)判斷是前攝像頭(CameraCharacteristics.LENS_FACING_FRONT)還是后攝像頭(CameraCharacteristics.LENS_FACING_BACK)
3.初始化ImageReader
private var jpegImageReader: ImageReader? = null
jpegImageReader = ImageReader.newInstance(imageSize.width, imageSize.height, ImageFormat.JPEG, 5)
jpegImageReader?.setOnImageAvailableListener(OnJpegImageAvailableListener(), cameraHandler)
......
private inner class OnJpegImageAvailableListener : ImageReader.OnImageAvailableListener {
private val dateFormat: DateFormat = SimpleDateFormat("yyyyMMddHHmmssSSS", Locale.getDefault())
private val cameraDir: String = "${Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM)}/Camera"
@WorkerThread
override fun onImageAvailable(imageReader: ImageReader) {
val image = imageReader.acquireNextImage()
val captureResult = captureResults.take()
if (image != null && captureResult != null) {
image.use {
val jpegByteBuffer = it.planes[0].buffer// Jpeg image data only occupy the planes[0].
val jpegByteArray = ByteArray(jpegByteBuffer.remaining())
jpegByteBuffer.get(jpegByteArray)
val width = it.width
val height = it.height
saveImageExecutor.execute {
val date = System.currentTimeMillis()
val title = "IMG_${dateFormat.format(date)}"http:// e.g. IMG_20190211100833786
val displayName = "$title.jpeg"http:// e.g. IMG_20190211100833786.jpeg
val path = "$cameraDir/$displayName"http:// e.g. /sdcard/DCIM/Camera/IMG_20190211100833786.jpeg
val orientation = captureResult[CaptureResult.JPEG_ORIENTATION]
val location = captureResult[CaptureResult.JPEG_GPS_LOCATION]
val longitude = location?.longitude ?: 0.0
val latitude = location?.latitude ?: 0.0
// Write the jpeg data into the specified file.
File(path).writeBytes(jpegByteArray)
// Insert the image information into the media store.
val values = ContentValues()
values.put(MediaStore.Images.ImageColumns.TITLE, title)
values.put(MediaStore.Images.ImageColumns.DISPLAY_NAME, displayName)
values.put(MediaStore.Images.ImageColumns.DATA, path)
values.put(MediaStore.Images.ImageColumns.DATE_TAKEN, date)
values.put(MediaStore.Images.ImageColumns.WIDTH, width)
values.put(MediaStore.Images.ImageColumns.HEIGHT, height)
values.put(MediaStore.Images.ImageColumns.ORIENTATION, orientation)
values.put(MediaStore.Images.ImageColumns.LONGITUDE, longitude)
values.put(MediaStore.Images.ImageColumns.LATITUDE, latitude)
contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
// Refresh the thumbnail of image.
val thumbnail = getThumbnail(path)
if (thumbnail != null) {
runOnUiThread {
thumbnailView.setImageBitmap(thumbnail)
thumbnailView.scaleX = 0.8F
thumbnailView.scaleY = 0.8F
thumbnailView.animate().setDuration(50).scaleX(1.0F).scaleY(1.0F).start()
}
}
}
}
}
}
}
ImageReader是獲取圖像數(shù)據(jù)的重要途徑,通過它可以獲取到不同格式的圖像數(shù)據(jù),例如JPEG、YUV、RAW等。通過ImageReader.newInstance(int width, int height, int format, int maxImages)創(chuàng)建ImageReader對(duì)象,有4個(gè)參數(shù):
- width:圖像數(shù)據(jù)的寬度
- height:圖像數(shù)據(jù)的高度
- format:圖像數(shù)據(jù)的格式,例如
ImageFormat.JPEG,ImageFormat.YUV_420_888等 - maxImages:最大Image個(gè)數(shù),Image對(duì)象池的大小,指定了能從ImageReader獲取Image對(duì)象的最大值,過多獲取緩沖區(qū)可能導(dǎo)致OOM,所以最好按照最少的需要去設(shè)置這個(gè)值
ImageReader其他相關(guān)的方法和回調(diào):
-
ImageReader.OnImageAvailableListener:有新圖像數(shù)據(jù)的回調(diào) -
acquireLatestImage():從ImageReader的隊(duì)列里面,獲取最新的Image,刪除舊的,如果沒有可用的Image,返回null -
acquireNextImage():獲取下一個(gè)最新的可用Image,沒有則返回null -
close():釋放與此ImageReader關(guān)聯(lián)的所有資源 -
getSurface():獲取為當(dāng)前ImageReader生成Image的Surface
4.打開相機(jī)設(shè)備
val cameraStateCallback = CameraStateCallback()
cameraManager.openCamera(cameraId, cameraStateCallback, mainHandler)
......
private inner class CameraStateCallback : CameraDevice.StateCallback() {
@MainThread
override fun onOpened(camera: CameraDevice) {
cameraDeviceFuture!!.set(camera)
cameraCharacteristicsFuture!!.set(getCameraCharacteristics(camera.id))
}
@MainThread
override fun onClosed(camera: CameraDevice) {
}
@MainThread
override fun onDisconnected(camera: CameraDevice) {
cameraDeviceFuture!!.set(camera)
closeCamera()
}
@MainThread
override fun onError(camera: CameraDevice, error: Int) {
cameraDeviceFuture!!.set(camera)
closeCamera()
}
}
cameraManager.openCamera(@NonNull String cameraId,@NonNull final CameraDevice.StateCallback callback, @Nullable Handler handler)的三個(gè)參數(shù):
- cameraId:攝像頭的唯一標(biāo)識(shí)
- callback:設(shè)備連接狀態(tài)變化的回調(diào)
- handler:回調(diào)執(zhí)行的Handler對(duì)象,傳入null則使用當(dāng)前的主線程Handler
其中CameraStateCallback回調(diào):
- onOpened:表示相機(jī)打開成功,可以真正開始使用相機(jī),創(chuàng)建Capture會(huì)話
- onDisconnected:當(dāng)相機(jī)斷開連接時(shí)回調(diào)該方法,需要進(jìn)行釋放相機(jī)的操作
- onError:當(dāng)相機(jī)打開失敗時(shí),需要進(jìn)行釋放相機(jī)的操作
- onClosed:調(diào)用Camera.close()后的回調(diào)方法
5.創(chuàng)建Capture Session
val sessionStateCallback = SessionStateCallback()
......
val cameraDevice = cameraDeviceFuture?.get()
cameraDevice?.createCaptureSession(outputs, sessionStateCallback, mainHandler)
......
private inner class SessionStateCallback : CameraCaptureSession.StateCallback() {
@MainThread
override fun onConfigureFailed(session: CameraCaptureSession) {
captureSessionFuture!!.set(session)
}
@MainThread
override fun onConfigured(session: CameraCaptureSession) {
captureSessionFuture!!.set(session)
}
@MainThread
override fun onClosed(session: CameraCaptureSession) {
}
}
這段的代碼核心方法是mCameraDevice.createCaptureSession()創(chuàng)建Capture會(huì)話,它接受了三個(gè)參數(shù):
- outputs:用于接受圖像數(shù)據(jù)的surface集合,這里傳入的是一個(gè)preview的surface
- callback:用于監(jiān)聽 Session 狀態(tài)的CameraCaptureSession.StateCallback對(duì)象
- handler:用于執(zhí)行CameraCaptureSession.StateCallback的Handler對(duì)象,傳入null則使用當(dāng)前的主線程Handler
6.創(chuàng)建CaptureRequest
CaptureRequest是向CameraCaptureSession提交Capture請(qǐng)求時(shí)的信息載體,其內(nèi)部包括了本次Capture的參數(shù)配置和接收?qǐng)D像數(shù)據(jù)的Surface
if (cameraDevice != null) {
previewImageRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
captureImageRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE)
}
......
val cameraDevice = cameraDeviceFuture?.get()
val captureSession = captureSessionFuture?.get()
val previewImageRequestBuilder = previewImageRequestBuilder!!
val captureImageRequestBuilder = captureImageRequestBuilder!!
if (cameraDevice != null && captureSession != null) {
val previewSurface = previewSurface!!
val previewDataSurface = previewDataSurface
previewImageRequestBuilder.addTarget(previewSurface)
// Avoid missing preview frame while capturing image.
captureImageRequestBuilder.addTarget(previewSurface)
if (previewDataSurface != null) {
previewImageRequestBuilder.addTarget(previewDataSurface)
// Avoid missing preview data while capturing image.
captureImageRequestBuilder.addTarget(previewDataSurface)
}
val previewRequest = previewImageRequestBuilder.build()
captureSession.setRepeatingRequest(previewRequest, RepeatingCaptureStateCallback(), mainHandler)
}
......
private inner class RepeatingCaptureStateCallback : CameraCaptureSession.CaptureCallback() {
@MainThread
override fun onCaptureStarted(session: CameraCaptureSession, request: CaptureRequest, timestamp: Long, frameNumber: Long) {
super.onCaptureStarted(session, request, timestamp, frameNumber)
}
@MainThread
override fun onCaptureCompleted(session: CameraCaptureSession, request: CaptureRequest, result: TotalCaptureResult) {
super.onCaptureCompleted(session, request, result)
}
}
除了模式的配置,CaptureRequest還可以配置很多其他信息,例如圖像格式、圖像分辨率、傳感器控制、閃光燈控制、3A(自動(dòng)對(duì)焦-AF、自動(dòng)曝光-AE和自動(dòng)白平衡-AWB)控制等。在createCaptureSession的回調(diào)中可以進(jìn)行設(shè)置,最后通過build()方法生成CaptureRequest對(duì)象。
7.預(yù)覽
Camera2中,通過連續(xù)重復(fù)的Capture實(shí)現(xiàn)預(yù)覽功能,每次Capture會(huì)把預(yù)覽畫面顯示到對(duì)應(yīng)的Surface上。連續(xù)重復(fù)的Capture操作通過
captureSession.setRepeatingRequest(previewRequest, RepeatingCaptureStateCallback(), mainHandler)
實(shí)現(xiàn),該方法有三個(gè)參數(shù):
- request:CaptureRequest對(duì)象
- listener:監(jiān)聽Capture 狀態(tài)的回調(diào)
- handler:用于執(zhí)行CameraCaptureSession.CaptureCallback的Handler對(duì)象,傳入null則使用當(dāng)前的主線程Handler
停止預(yù)覽使用mCaptureSession.stopRepeating()方法。
8.拍照
設(shè)置上面的request,session后,就可以真正的開始拍照操作
val captureImageRequest = captureImageRequestBuilder.build()
captureSession.capture(captureImageRequest, CaptureImageStateCallback(), mainHandler)
......
private inner class CaptureImageStateCallback : CameraCaptureSession.CaptureCallback() {
@MainThread
override fun onCaptureStarted(session: CameraCaptureSession, request: CaptureRequest, timestamp: Long, frameNumber: Long) {
super.onCaptureStarted(session, request, timestamp, frameNumber)
// Play the shutter click sound.
cameraHandler?.post { mediaActionSound.play(MediaActionSound.SHUTTER_CLICK) }
}
@MainThread
override fun onCaptureCompleted(session: CameraCaptureSession, request: CaptureRequest, result: TotalCaptureResult) {
super.onCaptureCompleted(session, request, result)
captureResults.put(result)
}
}
captureSession.capture()方法也有三個(gè)參數(shù),和mCaptureSession.setRepeatingRequest一樣:
- request:CaptureRequest對(duì)象
- listener:監(jiān)聽Capture 狀態(tài)的回調(diào)
- handler:用于執(zhí)行CameraCaptureSession.CaptureCallback的Handler對(duì)象,傳入null則使用當(dāng)前的主線程Handler
9.關(guān)閉相機(jī)
和其他硬件資源的使用一樣,當(dāng)我們不再需要使用相機(jī)時(shí)記得調(diào)用 CameraDevice.close() 方法及時(shí)關(guān)閉相機(jī)回收資源。關(guān)閉相機(jī)的操作至關(guān)重要,因?yàn)槿绻阋恢闭加孟鄼C(jī)資源,其他基于相機(jī)開發(fā)的功能都會(huì)無法正常使用,嚴(yán)重情況下直接導(dǎo)致其他相機(jī)相關(guān)的 APP 無法正常使用,當(dāng)相機(jī)被完全關(guān)閉的時(shí)候會(huì)通過 CameraStateCallback.onCllosed() 方法通知你相機(jī)已經(jīng)被關(guān)閉。那么在什么時(shí)候關(guān)閉相機(jī)最合適呢?個(gè)人的建議是在 onPause() 的時(shí)候就一定要關(guān)閉相機(jī),因?yàn)樵谶@個(gè)時(shí)候相機(jī)頁面已經(jīng)不是用戶關(guān)注的焦點(diǎn),大部分情況下已經(jīng)可以關(guān)閉相機(jī)了。
cameraDevice?.close()
previewDataImageReader?.close()
jpegImageReader?.close()
先后對(duì)CaptureSession,CameraDevice,ImageReader進(jìn)行close操作,釋放資源。
從 Camera1遷移到Camera2的建議
如果你的項(xiàng)目正在使用Camera1,并且打算從Camera1遷移到Camera2的話,希望以下幾個(gè)建議可以對(duì)你有所幫助:
- Camera1嚴(yán)格區(qū)分了預(yù)覽和拍照兩個(gè)流程,而Camera2則把這兩個(gè)流程都抽象成了Capture行為,所以建議你不要帶著過多的Camera1思維使用Camera2,避免因?yàn)樗季S上的束縛而無法充分利用Camera2靈活的 API;
- 如同Camera1一樣,Camera2的一些 API調(diào)用也會(huì)耗時(shí),所以建議你使用獨(dú)立的線程執(zhí)行所有的相機(jī)操作,盡量避免直接在主線程調(diào)用Camera2的API,HandlerThread 是一個(gè)不錯(cuò)的選擇;
- 可以認(rèn)為Camera1是Camera2的一個(gè)子集,也就是說Camera1能做的事情Camera2一定能做,反過來則不一定行得通;
- 如果你的應(yīng)用程序需要同時(shí)兼容Camera1 和Camera2,個(gè)人建議分開維護(hù),因?yàn)镃amera1蹩腳的API設(shè)計(jì)很可能讓Camera2靈活的API無法得到充分的發(fā)揮,另外將兩個(gè)設(shè)計(jì)上完全不兼容的東西攪和在一起帶來的痛苦可能遠(yuǎn)大于其帶來便利性,多寫一些冗余的代碼也許還更開心;
- 官方說Camera2的性能會(huì)更好,但在較早期的一些機(jī)器上運(yùn)行Camera2的性能并沒有比Camera1好多少;
- 當(dāng)設(shè)備的 Supported Hardware Level 低于FULL的時(shí)候,建議還是使用Camera1,因?yàn)镕ULL級(jí)別以下的 Camera2 能提供的功能幾乎和Camera1一樣,所以倒不如選擇更加穩(wěn)定的Camera1。
完整代碼:https://github.com/zhijunhong/custom_camera/tree/master/camera2
最后,如果此篇博文對(duì)你有所幫助,別忘了點(diǎn)個(gè)贊喲~