827 lines
28 KiB
Kotlin
827 lines
28 KiB
Kotlin
/*
|
|
* Copyright (C) 2016 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package cash.z.android.cameraview.api21
|
|
|
|
import android.Manifest
|
|
import android.annotation.TargetApi
|
|
import android.content.Context
|
|
import android.graphics.ImageFormat
|
|
import android.hardware.camera2.*
|
|
import android.hardware.camera2.params.StreamConfigurationMap
|
|
import android.media.ImageReader
|
|
import android.os.Handler
|
|
import android.util.Log
|
|
import android.util.SparseIntArray
|
|
import androidx.annotation.NonNull
|
|
import androidx.annotation.RequiresPermission
|
|
import cash.z.android.cameraview.CameraView
|
|
import cash.z.android.cameraview.base.*
|
|
import android.os.HandlerThread
|
|
|
|
|
|
|
|
@TargetApi(21)
|
|
internal open class Camera2(callback: CameraViewImpl.Callback, preview: PreviewImpl, context: Context) : CameraViewImpl(callback, preview) {
|
|
|
|
private val mCameraManager: CameraManager
|
|
|
|
var firebaseCallback: CameraView.FirebaseCallback? = null
|
|
|
|
private val mCameraDeviceCallback = object : CameraDevice.StateCallback() {
|
|
|
|
override fun onOpened(@NonNull camera: CameraDevice) {
|
|
mCamera = camera
|
|
mCallback.onCameraOpened()
|
|
startCaptureSession()
|
|
}
|
|
|
|
override fun onClosed(@NonNull camera: CameraDevice) {
|
|
mCallback.onCameraClosed()
|
|
}
|
|
|
|
override fun onDisconnected(@NonNull camera: CameraDevice) {
|
|
mCamera = null
|
|
}
|
|
|
|
override fun onError(@NonNull camera: CameraDevice, error: Int) {
|
|
Log.e(TAG, "onError: " + camera.id + " (" + error + ")")
|
|
mCamera = null
|
|
}
|
|
|
|
}
|
|
|
|
private val mSessionCallback = object : CameraCaptureSession.StateCallback() {
|
|
|
|
override fun onConfigured(@NonNull session: CameraCaptureSession) {
|
|
if (mCamera == null) {
|
|
return
|
|
}
|
|
mCaptureSession = session
|
|
updateAutoFocus()
|
|
updateFlash()
|
|
try {
|
|
mCaptureSession!!.setRepeatingRequest(
|
|
mPreviewRequestBuilder!!.build(),
|
|
mCaptureCallback, backgroundHandler
|
|
)
|
|
} catch (e: CameraAccessException) {
|
|
Log.e(TAG, "Failed to start camera preview because it couldn't access camera", e)
|
|
} catch (e: IllegalStateException) {
|
|
Log.e(TAG, "Failed to start camera preview.", e)
|
|
}
|
|
|
|
}
|
|
|
|
override fun onConfigureFailed(@NonNull session: CameraCaptureSession) {
|
|
Log.e(TAG, "Failed to configure capture session.")
|
|
}
|
|
|
|
override fun onClosed(@NonNull session: CameraCaptureSession) {
|
|
if (mCaptureSession != null && mCaptureSession == session) {
|
|
mCaptureSession = null
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
private var mCaptureCallback: PictureCaptureCallback = object : PictureCaptureCallback() {
|
|
|
|
override fun onPrecaptureRequired() {
|
|
mPreviewRequestBuilder!!.set(
|
|
CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
|
|
CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START
|
|
)
|
|
setState(Camera2.PictureCaptureCallback.STATE_PRECAPTURE)
|
|
try {
|
|
mCaptureSession!!.capture(mPreviewRequestBuilder!!.build(), this, backgroundHandler)
|
|
mPreviewRequestBuilder!!.set(
|
|
CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
|
|
CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_IDLE
|
|
)
|
|
} catch (e: CameraAccessException) {
|
|
Log.e(TAG, "Failed to run precapture sequence.", e)
|
|
}
|
|
|
|
}
|
|
|
|
override fun onReady() {
|
|
captureStillPicture()
|
|
}
|
|
|
|
}
|
|
|
|
private val mOnImageAvailableListener = ImageReader.OnImageAvailableListener { reader ->
|
|
|
|
reader.acquireNextImage().use { image ->
|
|
val planes = image.planes
|
|
if (planes.isNotEmpty()) {
|
|
System.err.println("camoorah : planes was empty: $firebaseCallback")
|
|
firebaseCallback?.onImageAvailable(image)
|
|
try{ image.close() } catch(t: Throwable){ System.err.println("camoorah : failed to close")}
|
|
} else {
|
|
System.err.println("planes was empty")
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
var cameraId: String? = null
|
|
|
|
private var mCameraCharacteristics: CameraCharacteristics? = null
|
|
|
|
var mCamera: CameraDevice? = null
|
|
|
|
var mCaptureSession: CameraCaptureSession? = null
|
|
|
|
var mPreviewRequestBuilder: CaptureRequest.Builder? = null
|
|
|
|
private var mImageReader: ImageReader? = null
|
|
|
|
private val mPreviewSizes = SizeMap()
|
|
|
|
private val mPictureSizes = SizeMap()
|
|
|
|
private var mFacing: Int = 0
|
|
|
|
private var mAspectRatio = Constants.DEFAULT_ASPECT_RATIO
|
|
|
|
private var mAutoFocus: Boolean = false
|
|
|
|
// Revert
|
|
override var flash: Int = 0
|
|
set(flash) {
|
|
if (this.flash == flash) {
|
|
return
|
|
}
|
|
val saved = this.flash
|
|
field = flash
|
|
if (mPreviewRequestBuilder != null) {
|
|
updateFlash()
|
|
if (mCaptureSession != null) {
|
|
try {
|
|
mCaptureSession!!.setRepeatingRequest(
|
|
mPreviewRequestBuilder!!.build(),
|
|
mCaptureCallback, backgroundHandler
|
|
)
|
|
} catch (e: CameraAccessException) {
|
|
field = saved
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
private var mDisplayOrientation: Int = 0
|
|
|
|
override val isCameraOpened: Boolean
|
|
get() = mCamera != null
|
|
|
|
override var facing: Int
|
|
get() = mFacing
|
|
@RequiresPermission(Manifest.permission.CAMERA) set(facing) {
|
|
if (mFacing == facing) {
|
|
return
|
|
}
|
|
mFacing = facing
|
|
if (isCameraOpened) {
|
|
stop()
|
|
start()
|
|
}
|
|
}
|
|
|
|
override val supportedAspectRatios: Set<AspectRatio>
|
|
get() = mPreviewSizes.ratios()
|
|
|
|
// Revert
|
|
override var autoFocus: Boolean
|
|
get() = mAutoFocus
|
|
set(autoFocus) {
|
|
if (mAutoFocus == autoFocus) {
|
|
return
|
|
}
|
|
mAutoFocus = autoFocus
|
|
if (mPreviewRequestBuilder != null) {
|
|
updateAutoFocus()
|
|
if (mCaptureSession != null) {
|
|
try {
|
|
mCaptureSession!!.setRepeatingRequest(
|
|
mPreviewRequestBuilder!!.build(),
|
|
mCaptureCallback, backgroundHandler
|
|
)
|
|
} catch (e: CameraAccessException) {
|
|
mAutoFocus = !mAutoFocus
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
init {
|
|
mCameraManager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
|
|
mPreview.setCallback(object : PreviewImpl.Callback {
|
|
override fun onSurfaceChanged() {
|
|
startCaptureSession()
|
|
}
|
|
})
|
|
}
|
|
|
|
@RequiresPermission(Manifest.permission.CAMERA)
|
|
override fun start(): Boolean {
|
|
if (!chooseCameraIdByFacing()) {
|
|
return false
|
|
}
|
|
startBackgroundThread()
|
|
collectCameraInfo()
|
|
prepareImageReader()
|
|
startOpeningCamera()
|
|
return true
|
|
}
|
|
|
|
override fun stop() {
|
|
stopBackgroundThread()
|
|
if (mCaptureSession != null) {
|
|
mCaptureSession!!.close()
|
|
mCaptureSession = null
|
|
}
|
|
if (mCamera != null) {
|
|
mCamera!!.close()
|
|
mCamera = null
|
|
}
|
|
if (mImageReader != null) {
|
|
mImageReader!!.close()
|
|
mImageReader = null
|
|
}
|
|
}
|
|
|
|
override val aspectRatio: AspectRatio get() = mAspectRatio
|
|
|
|
override fun setAspectRatio(ratio: AspectRatio?): Boolean {
|
|
if (ratio == null || ratio == mAspectRatio ||
|
|
!mPreviewSizes.ratios().contains(ratio)
|
|
) {
|
|
// TODO: Better error handling
|
|
return false
|
|
}
|
|
mAspectRatio = ratio
|
|
prepareImageReader()
|
|
if (mCaptureSession != null) {
|
|
mCaptureSession!!.close()
|
|
mCaptureSession = null
|
|
startCaptureSession()
|
|
}
|
|
return true
|
|
}
|
|
|
|
override fun takePicture() {
|
|
if (mAutoFocus) {
|
|
lockFocus()
|
|
} else {
|
|
captureStillPicture()
|
|
}
|
|
}
|
|
|
|
override fun setDisplayOrientation(displayOrientation: Int) {
|
|
mDisplayOrientation = displayOrientation
|
|
mPreview.setDisplayOrientation(mDisplayOrientation)
|
|
}
|
|
|
|
/**
|
|
*
|
|
* Chooses a camera ID by the specified camera facing ([.mFacing]).
|
|
*
|
|
* This rewrites [.mCameraId], [.mCameraCharacteristics], and optionally
|
|
* [.mFacing].
|
|
*/
|
|
private fun chooseCameraIdByFacing(): Boolean {
|
|
try {
|
|
val internalFacing = INTERNAL_FACINGS.get(mFacing)
|
|
val ids = mCameraManager.cameraIdList
|
|
if (ids.size == 0) { // No camera
|
|
throw RuntimeException("No camera available.")
|
|
}
|
|
for (id in ids) {
|
|
val characteristics = mCameraManager.getCameraCharacteristics(id)
|
|
val level = characteristics.get(
|
|
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL
|
|
)
|
|
if (level == null || level == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) {
|
|
continue
|
|
}
|
|
val internal = characteristics.get(CameraCharacteristics.LENS_FACING)
|
|
?: throw NullPointerException("Unexpected state: LENS_FACING null")
|
|
if (internal == internalFacing) {
|
|
cameraId = id
|
|
mCameraCharacteristics = characteristics
|
|
return true
|
|
}
|
|
}
|
|
// Not found
|
|
cameraId = ids[0]
|
|
mCameraCharacteristics = mCameraManager.getCameraCharacteristics(cameraId!!)
|
|
val level = mCameraCharacteristics!!.get(
|
|
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL
|
|
)
|
|
if (level == null || level == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) {
|
|
return false
|
|
}
|
|
val internal = mCameraCharacteristics!!.get(CameraCharacteristics.LENS_FACING)
|
|
?: throw NullPointerException("Unexpected state: LENS_FACING null")
|
|
var i = 0
|
|
val count = INTERNAL_FACINGS.size()
|
|
while (i < count) {
|
|
if (INTERNAL_FACINGS.valueAt(i) == internal) {
|
|
mFacing = INTERNAL_FACINGS.keyAt(i)
|
|
return true
|
|
}
|
|
i++
|
|
}
|
|
// The operation can reach here when the only camera device is an external one.
|
|
// We treat it as facing back.
|
|
mFacing = Constants.FACING_BACK
|
|
return true
|
|
} catch (e: CameraAccessException) {
|
|
throw RuntimeException("Failed to get a list of camera devices", e)
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
*
|
|
* Collects some information from [.mCameraCharacteristics].
|
|
*
|
|
* This rewrites [.mPreviewSizes], [.mPictureSizes], and optionally,
|
|
* [.mAspectRatio].
|
|
*/
|
|
private fun collectCameraInfo() {
|
|
val map = mCameraCharacteristics!!.get(
|
|
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP
|
|
) ?: throw IllegalStateException("Failed to get configuration map: " + cameraId!!)
|
|
mPreviewSizes.clear()
|
|
for (size in map.getOutputSizes(mPreview.outputClass)) {
|
|
val width = size.width
|
|
val height = size.height
|
|
if (width <= MAX_PREVIEW_WIDTH && height <= MAX_PREVIEW_HEIGHT) {
|
|
mPreviewSizes.add(Size(width, height))
|
|
}
|
|
}
|
|
mPictureSizes.clear()
|
|
collectPictureSizes(mPictureSizes, map)
|
|
for (ratio in mPreviewSizes.ratios()) {
|
|
if (!mPictureSizes.ratios().contains(ratio)) {
|
|
mPreviewSizes.remove(ratio)
|
|
}
|
|
}
|
|
|
|
if (!mPreviewSizes.ratios().contains(mAspectRatio)) {
|
|
mAspectRatio = mPreviewSizes.ratios().iterator().next()
|
|
}
|
|
}
|
|
|
|
protected open fun collectPictureSizes(sizes: SizeMap, map: StreamConfigurationMap) {
|
|
val outputSizes = map.getOutputSizes(ImageFormat.JPEG)
|
|
for (size in outputSizes) {
|
|
mPictureSizes.add(Size(size.width, size.height))
|
|
}
|
|
}
|
|
|
|
private fun prepareImageReader() {
|
|
if (mImageReader != null) {
|
|
mImageReader!!.close()
|
|
}
|
|
// val largest = mPictureSizes.sizes(mAspectRatio).last()
|
|
val previewSize = chooseOptimalSize()
|
|
mImageReader = ImageReader.newInstance(
|
|
previewSize.width / 4, previewSize.height / 4, ImageFormat.YUV_420_888, 2
|
|
)
|
|
mImageReader!!.setOnImageAvailableListener(mOnImageAvailableListener, backgroundHandler)
|
|
}
|
|
|
|
/**
|
|
*
|
|
* Starts opening a camera device.
|
|
*
|
|
* The result will be processed in [.mCameraDeviceCallback].
|
|
*/
|
|
@RequiresPermission(Manifest.permission.CAMERA)
|
|
private fun startOpeningCamera() {
|
|
try {
|
|
mCameraManager.openCamera(cameraId!!, mCameraDeviceCallback, backgroundHandler)
|
|
} catch (e: CameraAccessException) {
|
|
throw RuntimeException("Failed to open camera: " + cameraId!!, e)
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
*
|
|
* Starts a capture session for camera preview.
|
|
*
|
|
* This rewrites [.mPreviewRequestBuilder].
|
|
*
|
|
* The result will be continuously processed in [.mSessionCallback].
|
|
*/
|
|
fun startCaptureSession() {
|
|
if (!isCameraOpened || !mPreview.isReady || mImageReader == null) {
|
|
return
|
|
}
|
|
val previewSize = chooseOptimalSize()
|
|
mPreview.setBufferSize(previewSize.width, previewSize.height)
|
|
val surface = mPreview.surface
|
|
try {
|
|
mPreviewRequestBuilder = mCamera!!.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
|
|
mPreviewRequestBuilder!!.addTarget(surface)
|
|
mPreviewRequestBuilder!!.addTarget(mImageReader!!.surface)
|
|
mCamera!!.createCaptureSession(
|
|
listOf(surface, mImageReader!!.surface),
|
|
mSessionCallback, backgroundHandler
|
|
)
|
|
} catch (e: CameraAccessException) {
|
|
throw RuntimeException("Failed to start camera session")
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Chooses the optimal preview size based on [.mPreviewSizes] and the surface size.
|
|
*
|
|
* @return The picked size for camera preview.
|
|
*/
|
|
private fun chooseOptimalSize(): Size {
|
|
val surfaceLonger: Int
|
|
val surfaceShorter: Int
|
|
val surfaceWidth = mPreview.width
|
|
val surfaceHeight = mPreview.height
|
|
if (surfaceWidth < surfaceHeight) {
|
|
surfaceLonger = surfaceHeight
|
|
surfaceShorter = surfaceWidth
|
|
} else {
|
|
surfaceLonger = surfaceWidth
|
|
surfaceShorter = surfaceHeight
|
|
}
|
|
val candidates = mPreviewSizes.sizes(mAspectRatio)
|
|
|
|
// Pick the smallest of those big enough
|
|
for (size in candidates) {
|
|
if (size.width >= surfaceLonger && size.height >= surfaceShorter) {
|
|
return size
|
|
}
|
|
}
|
|
// If no size is big enough, pick the largest one.
|
|
return candidates.last()
|
|
}
|
|
|
|
/**
|
|
* Updates the internal state of auto-focus to [.mAutoFocus].
|
|
*/
|
|
fun updateAutoFocus() {
|
|
if (mAutoFocus) {
|
|
val modes = mCameraCharacteristics!!.get(
|
|
CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES
|
|
)
|
|
// Auto focus is not supported
|
|
if (modes == null || modes.size == 0 ||
|
|
modes.size == 1 && modes[0] == CameraCharacteristics.CONTROL_AF_MODE_OFF
|
|
) {
|
|
mAutoFocus = false
|
|
mPreviewRequestBuilder!!.set(
|
|
CaptureRequest.CONTROL_AF_MODE,
|
|
CaptureRequest.CONTROL_AF_MODE_OFF
|
|
)
|
|
} else {
|
|
mPreviewRequestBuilder!!.set(
|
|
CaptureRequest.CONTROL_AF_MODE,
|
|
CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE
|
|
)
|
|
}
|
|
} else {
|
|
mPreviewRequestBuilder!!.set(
|
|
CaptureRequest.CONTROL_AF_MODE,
|
|
CaptureRequest.CONTROL_AF_MODE_OFF
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Updates the internal state of flash to [.mFlash].
|
|
*/
|
|
fun updateFlash() {
|
|
when (flash) {
|
|
Constants.FLASH_OFF -> {
|
|
mPreviewRequestBuilder!!.set(
|
|
CaptureRequest.CONTROL_AE_MODE,
|
|
CaptureRequest.CONTROL_AE_MODE_ON
|
|
)
|
|
mPreviewRequestBuilder!!.set(
|
|
CaptureRequest.FLASH_MODE,
|
|
CaptureRequest.FLASH_MODE_OFF
|
|
)
|
|
}
|
|
Constants.FLASH_ON -> {
|
|
mPreviewRequestBuilder!!.set(
|
|
CaptureRequest.CONTROL_AE_MODE,
|
|
CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH
|
|
)
|
|
mPreviewRequestBuilder!!.set(
|
|
CaptureRequest.FLASH_MODE,
|
|
CaptureRequest.FLASH_MODE_OFF
|
|
)
|
|
}
|
|
Constants.FLASH_TORCH -> {
|
|
mPreviewRequestBuilder!!.set(
|
|
CaptureRequest.CONTROL_AE_MODE,
|
|
CaptureRequest.CONTROL_AE_MODE_ON
|
|
)
|
|
mPreviewRequestBuilder!!.set(
|
|
CaptureRequest.FLASH_MODE,
|
|
CaptureRequest.FLASH_MODE_TORCH
|
|
)
|
|
}
|
|
Constants.FLASH_AUTO -> {
|
|
mPreviewRequestBuilder!!.set(
|
|
CaptureRequest.CONTROL_AE_MODE,
|
|
CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH
|
|
)
|
|
mPreviewRequestBuilder!!.set(
|
|
CaptureRequest.FLASH_MODE,
|
|
CaptureRequest.FLASH_MODE_OFF
|
|
)
|
|
}
|
|
Constants.FLASH_RED_EYE -> {
|
|
mPreviewRequestBuilder!!.set(
|
|
CaptureRequest.CONTROL_AE_MODE,
|
|
CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE
|
|
)
|
|
mPreviewRequestBuilder!!.set(
|
|
CaptureRequest.FLASH_MODE,
|
|
CaptureRequest.FLASH_MODE_OFF
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Locks the focus as the first step for a still image capture.
|
|
*/
|
|
private fun lockFocus() {
|
|
mPreviewRequestBuilder!!.set(
|
|
CaptureRequest.CONTROL_AF_TRIGGER,
|
|
CaptureRequest.CONTROL_AF_TRIGGER_START
|
|
)
|
|
try {
|
|
mCaptureCallback.setState(PictureCaptureCallback.STATE_LOCKING)
|
|
mCaptureSession!!.capture(mPreviewRequestBuilder!!.build(), mCaptureCallback, backgroundHandler)
|
|
} catch (e: CameraAccessException) {
|
|
Log.e(TAG, "Failed to lock focus.", e)
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Captures a still picture.
|
|
*/
|
|
fun captureStillPicture() {
|
|
Log.e("camoorah", "capturing still picture")
|
|
try {
|
|
val captureRequestBuilder = mCamera!!.createCaptureRequest(
|
|
CameraDevice.TEMPLATE_STILL_CAPTURE
|
|
)
|
|
captureRequestBuilder.addTarget(mImageReader!!.surface)
|
|
captureRequestBuilder.set(
|
|
CaptureRequest.CONTROL_AF_MODE,
|
|
mPreviewRequestBuilder!!.get(CaptureRequest.CONTROL_AF_MODE)
|
|
)
|
|
when (flash) {
|
|
Constants.FLASH_OFF -> {
|
|
captureRequestBuilder.set(
|
|
CaptureRequest.CONTROL_AE_MODE,
|
|
CaptureRequest.CONTROL_AE_MODE_ON
|
|
)
|
|
captureRequestBuilder.set(
|
|
CaptureRequest.FLASH_MODE,
|
|
CaptureRequest.FLASH_MODE_OFF
|
|
)
|
|
}
|
|
Constants.FLASH_ON -> captureRequestBuilder.set(
|
|
CaptureRequest.CONTROL_AE_MODE,
|
|
CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH
|
|
)
|
|
Constants.FLASH_TORCH -> {
|
|
captureRequestBuilder.set(
|
|
CaptureRequest.CONTROL_AE_MODE,
|
|
CaptureRequest.CONTROL_AE_MODE_ON
|
|
)
|
|
captureRequestBuilder.set(
|
|
CaptureRequest.FLASH_MODE,
|
|
CaptureRequest.FLASH_MODE_TORCH
|
|
)
|
|
}
|
|
Constants.FLASH_AUTO -> captureRequestBuilder.set(
|
|
CaptureRequest.CONTROL_AE_MODE,
|
|
CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH
|
|
)
|
|
Constants.FLASH_RED_EYE -> captureRequestBuilder.set(
|
|
CaptureRequest.CONTROL_AE_MODE,
|
|
CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH
|
|
)
|
|
}
|
|
// Calculate JPEG orientation.
|
|
val sensorOrientation = mCameraCharacteristics!!.get(
|
|
CameraCharacteristics.SENSOR_ORIENTATION
|
|
)!!
|
|
captureRequestBuilder.set(
|
|
CaptureRequest.JPEG_ORIENTATION,
|
|
(sensorOrientation +
|
|
mDisplayOrientation * (if (mFacing == Constants.FACING_FRONT) 1 else -1) +
|
|
360) % 360
|
|
)
|
|
// Stop preview and capture a still picture.
|
|
mCaptureSession!!.stopRepeating()
|
|
mCaptureSession!!.capture(captureRequestBuilder.build(),
|
|
object : CameraCaptureSession.CaptureCallback() {
|
|
override fun onCaptureCompleted(
|
|
@NonNull session: CameraCaptureSession,
|
|
@NonNull request: CaptureRequest,
|
|
@NonNull result: TotalCaptureResult
|
|
) {
|
|
unlockFocus()
|
|
}
|
|
}, backgroundHandler
|
|
)
|
|
} catch (e: CameraAccessException) {
|
|
Log.e(TAG, "Cannot capture a still picture.", e)
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Unlocks the auto-focus and restart camera preview. This is supposed to be called after
|
|
* capturing a still picture.
|
|
*/
|
|
fun unlockFocus() {
|
|
mPreviewRequestBuilder!!.set(
|
|
CaptureRequest.CONTROL_AF_TRIGGER,
|
|
CaptureRequest.CONTROL_AF_TRIGGER_CANCEL
|
|
)
|
|
try {
|
|
mCaptureSession!!.capture(mPreviewRequestBuilder!!.build(), mCaptureCallback, backgroundHandler)
|
|
updateAutoFocus()
|
|
updateFlash()
|
|
mPreviewRequestBuilder!!.set(
|
|
CaptureRequest.CONTROL_AF_TRIGGER,
|
|
CaptureRequest.CONTROL_AF_TRIGGER_IDLE
|
|
)
|
|
mCaptureSession!!.setRepeatingRequest(mPreviewRequestBuilder!!.build(), mCaptureCallback, backgroundHandler)
|
|
mCaptureCallback.setState(PictureCaptureCallback.STATE_PREVIEW)
|
|
} catch (e: CameraAccessException) {
|
|
Log.e(TAG, "Failed to restart camera preview.", e)
|
|
}
|
|
}
|
|
|
|
var backgroundHandlerThread: HandlerThread? = null
|
|
var backgroundHandler: Handler? = null
|
|
|
|
/**
|
|
* Starts a background thread and its [Handler].
|
|
*/
|
|
private fun startBackgroundThread() {
|
|
backgroundHandlerThread = HandlerThread("CameraBackgroundProcessor")
|
|
backgroundHandlerThread?.start()
|
|
backgroundHandler = Handler(backgroundHandlerThread?.looper)
|
|
}
|
|
|
|
/**
|
|
* Stops the background thread and its [Handler].
|
|
*/
|
|
private fun stopBackgroundThread() {
|
|
backgroundHandlerThread?.quitSafely()
|
|
try {
|
|
backgroundHandlerThread?.join()
|
|
backgroundHandlerThread = null
|
|
backgroundHandler = null
|
|
} catch (e: InterruptedException) {
|
|
e.printStackTrace()
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* A [CameraCaptureSession.CaptureCallback] for capturing a still picture.
|
|
*/
|
|
private abstract class PictureCaptureCallback internal constructor() : CameraCaptureSession.CaptureCallback() {
|
|
|
|
private var mState: Int = 0
|
|
|
|
internal fun setState(state: Int) {
|
|
mState = state
|
|
}
|
|
|
|
override fun onCaptureProgressed(
|
|
@NonNull session: CameraCaptureSession,
|
|
@NonNull request: CaptureRequest, @NonNull partialResult: CaptureResult
|
|
) {
|
|
process(partialResult)
|
|
}
|
|
|
|
override fun onCaptureCompleted(
|
|
@NonNull session: CameraCaptureSession,
|
|
@NonNull request: CaptureRequest, @NonNull result: TotalCaptureResult
|
|
) {
|
|
process(result)
|
|
}
|
|
|
|
private fun process(@NonNull result: CaptureResult) {
|
|
when (mState) {
|
|
STATE_LOCKING -> {
|
|
val af = result.get(CaptureResult.CONTROL_AF_STATE)
|
|
if (af != null) {
|
|
if (af == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED || af == CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED) {
|
|
val ae = result.get(CaptureResult.CONTROL_AE_STATE)
|
|
if (ae == null || ae == CaptureResult.CONTROL_AE_STATE_CONVERGED) {
|
|
setState(STATE_CAPTURING)
|
|
onReady()
|
|
} else {
|
|
setState(STATE_LOCKED)
|
|
onPrecaptureRequired()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
STATE_PRECAPTURE -> {
|
|
val ae = result.get(CaptureResult.CONTROL_AE_STATE)
|
|
if (ae == null || ae == CaptureResult.CONTROL_AE_STATE_PRECAPTURE ||
|
|
ae == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED ||
|
|
ae == CaptureResult.CONTROL_AE_STATE_CONVERGED
|
|
) {
|
|
setState(STATE_WAITING)
|
|
}
|
|
}
|
|
STATE_WAITING -> {
|
|
val ae = result.get(CaptureResult.CONTROL_AE_STATE)
|
|
if (ae == null || ae != CaptureResult.CONTROL_AE_STATE_PRECAPTURE) {
|
|
setState(STATE_CAPTURING)
|
|
onReady()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called when it is ready to take a still picture.
|
|
*/
|
|
abstract fun onReady()
|
|
|
|
/**
|
|
* Called when it is necessary to run the precapture sequence.
|
|
*/
|
|
abstract fun onPrecaptureRequired()
|
|
|
|
companion object {
|
|
|
|
internal val STATE_PREVIEW = 0
|
|
internal val STATE_LOCKING = 1
|
|
internal val STATE_LOCKED = 2
|
|
internal val STATE_PRECAPTURE = 3
|
|
internal val STATE_WAITING = 4
|
|
internal val STATE_CAPTURING = 5
|
|
}
|
|
|
|
}
|
|
|
|
companion object {
|
|
|
|
private val TAG = "Camera2"
|
|
|
|
private val INTERNAL_FACINGS = SparseIntArray()
|
|
|
|
init {
|
|
INTERNAL_FACINGS.put(Constants.FACING_BACK, CameraCharacteristics.LENS_FACING_BACK)
|
|
INTERNAL_FACINGS.put(Constants.FACING_FRONT, CameraCharacteristics.LENS_FACING_FRONT)
|
|
}
|
|
|
|
/**
|
|
* Max preview width that is guaranteed by Camera2 API
|
|
*/
|
|
private val MAX_PREVIEW_WIDTH = 1920
|
|
|
|
/**
|
|
* Max preview height that is guaranteed by Camera2 API
|
|
*/
|
|
private val MAX_PREVIEW_HEIGHT = 1080
|
|
}
|
|
|
|
}
|