上一章《Camera2 預(yù)覽》我們學(xué)習(xí)了如何配置預(yù)覽呼渣,接下來我們來學(xué)習(xí)如何拍照媒楼。
閱讀完本章,你將會學(xué)到以下幾個知識點:
- 理解 Capture 工作流程
- 如何拍攝單張照片
- 如何連續(xù)拍攝多張照片
- 如何連拍照片
- 如何配置縮略圖尺寸
- 如何播放快門音效
- 如何矯正圖片方向
- 如何切換前后置攝像頭
你可以在 https://github.com/darylgo/Camera2Sample 下載相關(guān)的源碼镇眷,并且切換到 Tutorial4 標(biāo)簽下吧史。
1 理解 Capture 工作流程
在正式介紹如何拍照之前,我們有必要深入理解幾種不同模式的 Capture 的工作流程蕴轨,只要理解它們的工作流程就很容易掌握各種拍照模式的實現(xiàn)原理港谊,在第一章《Camera2 概覽》 里我們介紹了 Capture 有以下幾種不同模式:
單次模式(One-shot):指的是只執(zhí)行一次的 Capture 操作,例如設(shè)置閃光燈模式橙弱、對焦模式和拍一張照片等歧寺。多個單次模式的 Capture 會進入隊列按順序執(zhí)行。
多次模式(Burst):指的是連續(xù)多次執(zhí)行指定的 Capture 操作棘脐,該模式和多次執(zhí)行單次模式的最大區(qū)別是連續(xù)多次 Capture 期間不允許插入其他任何 Capture 操作斜筐,例如連續(xù)拍攝 100 張照片,在拍攝這 100 張照片期間任何新的 Capture 請求都會排隊等待蛀缝,直到拍完 100 張照片顷链。多組多次模式的 Capture 會進入隊列按順序執(zhí)行。
重復(fù)模式(Repeating):指的是不斷重復(fù)執(zhí)行指定的 Capture 操作屈梁,當(dāng)有其他模式的 Capture 提交時會暫停該模式嗤练,轉(zhuǎn)而執(zhí)行其他被模式的 Capture,當(dāng)其他模式的 Capture 執(zhí)行完畢后又會自動恢復(fù)繼續(xù)執(zhí)行該模式的 Capture在讶,例如顯示預(yù)覽畫面就是不斷 Capture 獲取每一幀畫面煞抬。該模式的 Capture 是全局唯一的,也就是新提交的重復(fù)模式 Capture 會覆蓋舊的重復(fù)模式 Capture构哺。
我們舉個例子來進一步說明上面三種模式革答,假設(shè)我們的相機應(yīng)用程序開啟了預(yù)覽,所以會提交一個重復(fù)模式的 Capture 用于不斷獲取預(yù)覽畫面,然后我們提交一個單次模式的 Capture残拐,接著我們又提交了一組連續(xù)三次的多次模式的 Capture途茫,這些不同模式的 Capture 會按照下圖所示被執(zhí)行:
下面是幾個重要的注意事項:
無論 Capture 以何種模式被提交,它們都是按順序串行執(zhí)行的蹦骑,不存在并行執(zhí)行的情況慈省。
重復(fù)模式是一個比較特殊的模式,因為它會保留我們提交的 CaptureRequest 對象用于不斷重復(fù)執(zhí)行 Capture 操作眠菇,所以大多數(shù)情況下重復(fù)模式的 CaptureRequest 和其他模式的 CaptureRequest 是獨立的边败,這就會導(dǎo)致重復(fù)模式的參數(shù)和其他模式的參數(shù)會有一定的差異,例如重復(fù)模式不會配置
CaptureRequest.AF_TRIGGER_START
捎废,因為這會導(dǎo)致相機不斷觸發(fā)對焦的操作笑窜。如果某一次的 Capture 沒有配置預(yù)覽的 Surface,例如拍照的時候登疗,就會導(dǎo)致本次 Capture 不會將畫面輸出到預(yù)覽的 Surface 上排截,進而導(dǎo)致預(yù)覽畫面卡頓的情況,所以大部分情況下我們都會將預(yù)覽的 Surface 添加到所有的 CaptureRequest 里辐益。
2 如何拍攝單張照片
拍攝單張照片是最簡單的拍照模式断傲,它使用的就是單次模式的 Capture,我們會使用 ImageReader 創(chuàng)建一個接收照片的 Surface智政,并且把它添加到 CaptureRequest 里提交給相機進行拍照认罩,最后通過 ImageReader 的回調(diào)獲取 Image 對象,進而獲取 JPEG 圖像數(shù)據(jù)進行保存续捂。
2.1 定義回調(diào)接口
當(dāng)拍照完成的時候我們會得到兩個數(shù)據(jù)對象垦垂,一個是通過 onImageAvailable()
回調(diào)給我們的存儲圖像數(shù)據(jù)的 Image,一個是通過 onCaptureCompleted()
回調(diào)給我們的存儲拍照信息的 CaptureResult牙瓢,它們是一一對應(yīng)的劫拗,所以我們定義了如下兩個回調(diào)接口:
private val captureResults: BlockingQueue<CaptureResult> = LinkedBlockingDeque()
private inner class CaptureImageStateCallback : CameraCaptureSession.CaptureCallback() {
@MainThread
override fun onCaptureCompleted(session: CameraCaptureSession, request: CaptureRequest, result: TotalCaptureResult) {
super.onCaptureCompleted(session, request, result)
captureResults.put(result)
}
}
private inner class OnJpegImageAvailableListener : ImageReader.OnImageAvailableListener {
@WorkerThread
override fun onImageAvailable(imageReader: ImageReader) {
val image = imageReader.acquireNextImage()
val captureResult = captureResults.take()
if (image != null && captureResult != null) {
// Save image into sdcard.
}
}
}
2.2 創(chuàng)建 ImageReader
創(chuàng)建 ImageReader 需要我們指定照片的大小,所以首先我們要獲取支持的照片尺寸列表矾克,并且從中篩選出合適的尺寸页慷,假設(shè)我們要求照片的尺寸最大不能超過 4032x3024,并且比例必須是 4:3胁附,所以會有如下篩選尺寸的代碼片段:
@WorkerThread
private fun getOptimalSize(cameraCharacteristics: CameraCharacteristics, clazz: Class<*>, maxWidth: Int, maxHeight: Int): Size? {
val streamConfigurationMap = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
val supportedSizes = streamConfigurationMap?.getOutputSizes(clazz)
return getOptimalSize(supportedSizes, maxWidth, maxHeight)
}
@AnyThread
private fun getOptimalSize(supportedSizes: Array<Size>?, maxWidth: Int, maxHeight: Int): Size? {
val aspectRatio = maxWidth.toFloat() / maxHeight
if (supportedSizes != null) {
for (size in supportedSizes) {
if (size.width.toFloat() / size.height == aspectRatio && size.height <= maxHeight && size.width <= maxWidth) {
return size
}
}
}
return null
}
接著我們就可以篩選出合適的尺寸差购,然后創(chuàng)建一個圖像格式是 JPEG 的 ImageReader 對象,并且獲取它的 Surface:
val imageSize = getOptimalSize(cameraCharacteristics, ImageReader::class.java, maxWidth, maxHeight)!!
jpegImageReader = ImageReader.newInstance(imageSize.width, imageSize.height, ImageFormat.JPEG, 5)
jpegImageReader?.setOnImageAvailableListener(OnJpegImageAvailableListener(), cameraHandler)
jpegSurface = jpegImageReader?.surface
2.3 創(chuàng)建 CaptureRequest
接下來我們使用 TEMPLATE_STILL_CAPTURE
模板創(chuàng)建一個用于拍照的 CaptureRequest.Builder 對象汉嗽,并且添加拍照的 Surface 和預(yù)覽的 Surface 到其中:
captureImageRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE)
captureImageRequestBuilder.addTarget(previewDataSurface)
captureImageRequestBuilder.addTarget(jpegSurface)
你可能會疑問為什么拍照用的 CaptureRequest 對象需要添加預(yù)覽的 Surface,這一點我們在前面有解釋過了找蜜,如果某一次的 Capture 沒有配置預(yù)覽的 Surface饼暑,例如拍照的時候,就會導(dǎo)致本次 Capture 不會將畫面輸出到預(yù)覽的 Surface 上,進而導(dǎo)致預(yù)覽畫面卡頓的情況弓叛,所以大部分情況下我們都會將預(yù)覽的 Surface 添加到所有的 CaptureRequest 里彰居。
2.4 矯正 JPEG 圖片方向
在 《Camera2 預(yù)覽》 里我們介紹了一些方向的概念,也提到了攝像頭傳感器的方向很多時候都不是 0°撰筷,這就會導(dǎo)致我們拍出來的照片方向是錯誤的陈惰,例如手機攝像頭傳感器方向是 90° 的時候,垂直拿著手機拍出來的照片很可能是橫著的:
在進行圖片方向矯正的時候毕籽,我們的目的是做到所見即所得抬闯,也就是用戶在預(yù)覽畫面里看到的是什么樣,輸出的圖片就是什么樣关筒。為了做到圖片所見即所得溶握,我們要同時考慮設(shè)備方向和攝像頭傳感器方向,下面是一段來自官方的圖片矯正代碼:
private fun getJpegOrientation(cameraCharacteristics: CameraCharacteristics, deviceOrientation: Int): Int {
var myDeviceOrientation = deviceOrientation
if (myDeviceOrientation == android.view.OrientationEventListener.ORIENTATION_UNKNOWN) {
return 0
}
val sensorOrientation = cameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION)!!
// Round device orientation to a multiple of 90
myDeviceOrientation = (myDeviceOrientation + 45) / 90 * 90
// Reverse device orientation for front-facing cameras
val facingFront = cameraCharacteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT
if (facingFront) {
myDeviceOrientation = -myDeviceOrientation
}
// Calculate desired JPEG orientation relative to camera orientation to make
// the image upright relative to the device orientation
return (sensorOrientation + myDeviceOrientation + 360) % 360
}
如果你已經(jīng)理解 《Camera2 預(yù)覽》 里我們介紹的一些方向概念蒸播,那么上面這段代碼其實就很容易理解睡榆,唯一特別的地方是前置攝像頭輸出的畫面底層默認做了鏡像的翻轉(zhuǎn)才能保證我們在預(yù)覽的時候看到的畫面就想照鏡子一樣,所以前置攝像頭給的 SENSOR_ORIENTATION 值也是經(jīng)過鏡像的袍榆,但是相機在輸出 JPEG 的時候并沒有進行鏡像操作胀屿,所以在計算 JPEG 矯正角度的時候要對這個默認鏡像的操作進行逆向鏡像。
計算出圖片的矯正角度后包雀,我們要通過 CaptureRequest.JPEG_ORIENTATION
配置這個角度宿崭,相機在拍照輸出 JPEG 圖像的時候會參考這個角度值從以下兩種方式選一種進行圖像方向矯正:
- 直接對圖像進行旋轉(zhuǎn),并且將 Exif 的 ORIENTATION 標(biāo)簽賦值為 0馏艾。
- 不對圖像進行旋轉(zhuǎn)劳曹,而是將旋轉(zhuǎn)信息寫入 Exif 的 ORIENTATION 標(biāo)簽里。
客戶端在顯示圖片的時候一定要去檢查 Exif 的ORIENTATION 標(biāo)簽的值琅摩,并且根據(jù)這個值對圖片進行對應(yīng)角度的旋轉(zhuǎn)才能保證圖片顯示方向是正確的铁孵。
val deviceOrientation = deviceOrientationListener.orientation
val jpegOrientation = getJpegOrientation(cameraCharacteristics, deviceOrientation)
captureImageRequestBuilder[CaptureRequest.JPEG_ORIENTATION] = jpegOrientation
2.5 設(shè)置縮略圖尺寸
相機在輸出 JPEG 圖片的時候,同時會根據(jù)我們通過 CaptureRequest.JPEG_THUMBNAIL_SZIE
配置的縮略圖尺寸生成一張縮略圖寫入圖片的 Exif 信息里房资。在設(shè)置縮略圖尺寸之前蜕劝,我們首先要獲取相機支持哪些縮略圖尺寸,與獲取預(yù)覽尺寸或照片尺寸列表方式不一樣的是轰异,縮略圖尺寸列表是直接通過 CameraCharacteristics.JPEG_AVAILABLE_THUMBNAIL_SIZES
獲取的岖沛。配置縮略圖尺寸的代碼如下所示:
val availableThumbnailSizes = cameraCharacteristics[CameraCharacteristics.JPEG_AVAILABLE_THUMBNAIL_SIZES]
val thumbnailSize = getOptimalSize(availableThumbnailSizes, maxWidth, maxHeight)
在獲取圖片縮略圖的時候,我們不能總是假設(shè)圖片一定會在 Exif 寫入縮略圖搭独,當(dāng) Exif 里面沒有縮略圖數(shù)據(jù)的時候婴削,我們要轉(zhuǎn)而直接 Decode 原圖獲取縮略圖,另外無論是原圖還是縮略圖牙肝,都要根據(jù) Exif 的 ORIENTATION 角度進行角度矯正才能正確顯示唉俗,下面是我們 Demo 中獲取圖片縮略圖的代碼:
@WorkerThread
private fun getThumbnail(jpegPath: String): Bitmap? {
val exifInterface = ExifInterface(jpegPath)
val orientationFlag = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)
val orientation = when (orientationFlag) {
ExifInterface.ORIENTATION_NORMAL -> 0.0F
ExifInterface.ORIENTATION_ROTATE_90 -> 90.0F
ExifInterface.ORIENTATION_ROTATE_180 -> 180.0F
ExifInterface.ORIENTATION_ROTATE_270 -> 270.0F
else -> 0.0F
}
var thumbnail = if (exifInterface.hasThumbnail()) {
exifInterface.thumbnailBitmap
} else {
val options = BitmapFactory.Options()
options.inSampleSize = 16
BitmapFactory.decodeFile(jpegPath, options)
}
if (orientation != 0.0F && thumbnail != null) {
val matrix = Matrix()
matrix.setRotate(orientation)
thumbnail = Bitmap.createBitmap(thumbnail, 0, 0, thumbnail.width, thumbnail.height, matrix, true)
}
return thumbnail
}
2.6 設(shè)置定位信息
拍照的時候嗤朴,通常都會在圖片的 Exif 寫入定位信息,我們可以通過 CaptureRequest.JPEG_GPS_LOCATION
配置定位信息虫溜,代碼如下:
@WorkerThread
private fun getLocation(): Location? {
val locationManager = getSystemService(LocationManager::class.java)
if (locationManager != null && ContextCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
return locationManager.getLastKnownLocation(LocationManager.PASSIVE_PROVIDER)
}
return null
}
val location = getLocation()
captureImageRequestBuilder[CaptureRequest.JPEG_GPS_LOCATION] = location
2.7 播放快門音效
在進行拍照之前雹姊,我們還需要配置拍照時播放的快門音效,因為 Camera2 和 Camera1 不一樣衡楞,拍照時不會有任何聲音吱雏,需要我們在適當(dāng)?shù)臅r候通過 MediaSoundPlayer 播放快門音效,通常情況我們是在 CaptureStateCallback.onCaptureStarted()
回調(diào)的時候播放快門音效:
private val mediaActionSound: MediaActionSound = MediaActionSound()
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)
}
}
2.8 拍照并保存圖片
經(jīng)過一連串的配置之后瘾境,我們終于可以開拍照了歧杏,直接調(diào)用 CameraCaptureSession.capture()
方法把 CaptureRequest 對象提交給相機就可以等待相機輸出圖片了,該方法要求我們設(shè)置三個參數(shù):
- request:本次 Capture 操作使用的 CaptureRequest 對象寄雀。
- listener:監(jiān)聽 Capture 狀態(tài)的回調(diào)接口得滤。
- handler:回調(diào) Capture 狀態(tài)監(jiān)聽接口的 Handler 對象。
captureSession.capture(captureImageRequest, CaptureImageStateCallback(), mainHandler)
如果一切順利盒犹,相機在拍照完成的時候會通過 CaptureStateCallback.onCaptureCompleted()
回調(diào)一個 CaptureResult 對象給我們懂更,里面包含了本次拍照的所有信息,另外還會通過 OnImageAvailableListener.onImageAvailable()
回調(diào)一個代表圖像數(shù)據(jù)的 Image 對象給我們急膀。在我們的 Demo 中沮协,我們將獲取到的 CaptureResult 對象保存到一個阻塞隊列中,在 OnImageAvailableListener.onImageAvailable()
回調(diào)的時候就從這個阻塞隊列獲取 CaptureResult 對象卓嫂,結(jié)合 Image 對象對圖片進行保存操作慷暂,并且還會在圖片保存完畢的時候獲取圖片的縮略圖用于刷新 UI,代碼如下所示:
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()
}
}
}
}
}
}
}
2.9 前置攝像頭拍照的鏡像問題
如果你使用前置攝像頭進行拍照晨雳,雖然照片的方向已經(jīng)被我們矯正了行瑞,但是你會發(fā)現(xiàn)畫面卻是相反的,例如你在預(yù)覽的時候人臉在左邊餐禁,拍出來的照片人臉卻是在右邊血久。出現(xiàn)這個問題的原因是默認情況下相機不會對 JPEG 圖像進行鏡像操作,導(dǎo)致輸出的原始畫面是非鏡像的帮非。解決這個問題的一個辦法是拿到 JPEG 數(shù)據(jù)之后再次對圖像進行鏡像操作氧吐,然后才保存圖片。
3 如何連續(xù)拍攝多張圖片
在我們的 Demo 中有一個特殊的拍照功能末盔,就是當(dāng)用戶雙擊快門按鈕的時候會連續(xù)拍攝 10 張照片筑舅,其實現(xiàn)原理就是采用了多次模式的 Capture,所有的配置流程和拍攝單張照片一樣陨舱,唯一的區(qū)別是我們使用 CameraCaptureSession.captureBurst()
進行拍照翠拣,該方法要求我們傳遞一下三個參數(shù):
- requests:按順序連續(xù)執(zhí)行的 CaptureRequest 對象列表,每一個 CaptureRequest 對象都可以有自己的配置游盲,在我們的 Demo 里出于簡化的目的误墓,10 個 CaptureRequest 對象實際上的都是同一個邦尊。
- listener:監(jiān)聽 Capture 狀態(tài)的回調(diào)接口,需要注意的是有多少個 CaptureRequest 對象就會回調(diào)該接口多少次优烧。
- handler:回調(diào) Capture 狀態(tài)監(jiān)聽接口的 Handler 對象。
val captureImageRequest = captureImageRequestBuilder.build()
val captureImageRequests = mutableListOf<CaptureRequest>()
for (i in 1..burstNumber) {
captureImageRequests.add(captureImageRequest)
}
captureSession.captureBurst(captureImageRequests, CaptureImageStateCallback(), mainHandler)
接下來所有的流程就和拍攝單招照片一樣了链峭,每輸出一張圖片我們就將其保存到 SD 卡并且刷新媒體庫和縮略圖畦娄。
4 如何連拍
連拍這個功能在 Camera2 出現(xiàn)之前是不可能實現(xiàn)的,現(xiàn)在我們只需要使用重復(fù)模式的 Capture 就可以輕松實現(xiàn)連拍功能弊仪。在《Camera2 預(yù)覽》里我們使用了重復(fù)模式的 Capture 來實現(xiàn)預(yù)覽功能熙卡,而這一次我們不僅要用該模式進行預(yù)覽,還要在預(yù)覽的同時也輸出照片励饵,所以我們會使用 CameraCaptureSession.setRepeatingRequest()
方法開始進行連拍:
val captureImageRequest = captureImageRequestBuilder.build()
captureSession.setRepeatingRequest(captureImageRequest, CaptureImageStateCallback(), mainHandler)
停止連拍有以下兩種方式:
- 調(diào)用
CameraCaptueSession.stopRepeating()
方法停止重復(fù)模式的 Capture驳癌,但是這會導(dǎo)致預(yù)覽也停止。 - 調(diào)用
CameraCaptueSession.setRepeatingRequest()
方法并且使用預(yù)覽的 CaptureRequest 對象役听,停止輸出照片颓鲜。
在我們的 Demo 里使用了第二種方式:
@MainThread
private fun stopCaptureImageContinuously() {
// Restart preview to stop the continuous image capture.
startPreview()
}
5 如何切換前后置攝像頭
切換前后置攝像頭是一個很常見的功能,雖然和本章的主要內(nèi)容不相關(guān)典予,但是在 Demo 中已經(jīng)實現(xiàn)甜滨,所以這里也順便提一下。我們只要按照以下順序進行操作就可以輕松實現(xiàn)前后置攝像頭的切換:
- 關(guān)閉當(dāng)前攝像頭
- 開啟新的攝像頭
- 創(chuàng)建新的 Session
- 開啟預(yù)覽
下面是代碼片段瘤袖,詳細代碼大家可以自行查看 Demo 源碼:
@MainThread
private fun switchCamera() {
val cameraDevice = cameraDeviceFuture?.get()
val oldCameraId = cameraDevice?.id
val newCameraId = if (oldCameraId == frontCameraId) backCameraId else frontCameraId
if (newCameraId != null) {
closeCamera()
openCamera(newCameraId)
createCaptureRequestBuilders()
setPreviewSize(MAX_PREVIEW_WIDTH, MAX_PREVIEW_HEIGHT)
setImageSize(MAX_IMAGE_WIDTH, MAX_IMAGE_HEIGHT)
createSession()
startPreview()
}
}
6 總結(jié)
本章主要講述了如何實現(xiàn)幾種常見的拍照模式衣摩,其核心要領(lǐng)就是理解【重復(fù)模式】、【單詞模式】和【多次模式】的工作流程捂敌,根據(jù)實際業(yè)務(wù)情況靈活運用艾扮,下面是幾個小建議:
- 重復(fù)模式和多次模式都可以實現(xiàn)連拍功能,其中重復(fù)模式適合沒有連拍上限的情況占婉,而多次模式適合有連拍上限的情況泡嘴。
- 一個 CaptureRequest 可以添加多個 Surface,這就意味著你可以同時拍攝多張照片锐涯。
- 拍照獲取 CaptureResult 和 Image 對象走的是兩個不同的回調(diào)接口磕诊,靈活運用子線程的阻塞操作可以簡化你的代碼邏輯。