2023-09-01 13:08:33 +02:00
|
|
|
package com.mrousavy.camera.core
|
2023-03-13 14:23:19 +01:00
|
|
|
|
2023-08-21 12:50:14 +02:00
|
|
|
import android.graphics.ImageFormat
|
2023-03-13 14:23:19 +01:00
|
|
|
import android.hardware.camera2.CameraCharacteristics
|
|
|
|
import android.hardware.camera2.CameraManager
|
|
|
|
import android.hardware.camera2.CameraMetadata
|
|
|
|
import android.os.Build
|
|
|
|
import android.util.Range
|
|
|
|
import android.util.Size
|
|
|
|
import com.facebook.react.bridge.Arguments
|
|
|
|
import com.facebook.react.bridge.ReadableArray
|
|
|
|
import com.facebook.react.bridge.ReadableMap
|
2023-08-21 12:50:14 +02:00
|
|
|
import com.mrousavy.camera.extensions.getPhotoSizes
|
|
|
|
import com.mrousavy.camera.extensions.getVideoSizes
|
2023-10-24 14:27:47 +02:00
|
|
|
import com.mrousavy.camera.extensions.toJSValue
|
2023-10-24 11:19:03 +02:00
|
|
|
import com.mrousavy.camera.types.AutoFocusSystem
|
2023-10-24 14:27:47 +02:00
|
|
|
import com.mrousavy.camera.types.DeviceType
|
2023-10-17 11:49:04 +02:00
|
|
|
import com.mrousavy.camera.types.HardwareLevel
|
|
|
|
import com.mrousavy.camera.types.LensFacing
|
|
|
|
import com.mrousavy.camera.types.Orientation
|
|
|
|
import com.mrousavy.camera.types.PixelFormat
|
|
|
|
import com.mrousavy.camera.types.VideoStabilizationMode
|
2023-10-24 14:27:47 +02:00
|
|
|
import kotlin.math.atan2
|
|
|
|
import kotlin.math.sqrt
|
2023-03-13 14:23:19 +01:00
|
|
|
|
2023-08-21 12:50:14 +02:00
|
|
|
class CameraDeviceDetails(private val cameraManager: CameraManager, private val cameraId: String) {
|
2023-03-13 14:23:19 +01:00
|
|
|
private val characteristics = cameraManager.getCameraCharacteristics(cameraId)
|
2023-08-21 12:50:14 +02:00
|
|
|
private val hardwareLevel = HardwareLevel.fromCameraCharacteristics(characteristics)
|
2023-03-13 14:23:19 +01:00
|
|
|
private val capabilities = characteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES) ?: IntArray(0)
|
|
|
|
private val extensions = getSupportedExtensions()
|
|
|
|
|
|
|
|
// device characteristics
|
2023-09-21 11:20:33 +02:00
|
|
|
private val isMultiCam = capabilities.contains(11) // TODO: CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA
|
|
|
|
private val supportsDepthCapture = capabilities.contains(8) // TODO: CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_DEPTH_OUTPUT
|
2023-03-13 14:23:19 +01:00
|
|
|
private val supportsRawCapture = capabilities.contains(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW)
|
2023-09-21 11:20:33 +02:00
|
|
|
private val supportsLowLightBoost = extensions.contains(4) // TODO: CameraExtensionCharacteristics.EXTENSION_NIGHT
|
2023-08-21 12:50:14 +02:00
|
|
|
private val lensFacing = LensFacing.fromCameraCharacteristics(characteristics)
|
2023-03-13 14:23:19 +01:00
|
|
|
private val hasFlash = characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE) ?: false
|
2023-09-21 11:20:33 +02:00
|
|
|
private val focalLengths =
|
|
|
|
characteristics.get(CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS)
|
|
|
|
// 35mm is the film standard sensor size
|
|
|
|
?: floatArrayOf(35f)
|
2023-03-13 14:23:19 +01:00
|
|
|
private val sensorSize = characteristics.get(CameraCharacteristics.SENSOR_INFO_PHYSICAL_SIZE)!!
|
2023-09-01 15:07:16 +02:00
|
|
|
private val sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION)!!
|
2023-09-21 11:20:33 +02:00
|
|
|
private val name = (
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
|
|
|
characteristics.get(CameraCharacteristics.INFO_VERSION)
|
|
|
|
} else {
|
|
|
|
null
|
|
|
|
}
|
|
|
|
) ?: "$lensFacing ($cameraId)"
|
2023-03-13 14:23:19 +01:00
|
|
|
|
|
|
|
// "formats" (all possible configurations for this device)
|
2023-09-21 11:20:33 +02:00
|
|
|
private val zoomRange = (
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
|
|
characteristics.get(CameraCharacteristics.CONTROL_ZOOM_RATIO_RANGE)
|
|
|
|
} else {
|
|
|
|
null
|
|
|
|
}
|
|
|
|
) ?: Range(1f, characteristics.get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM) ?: 1f)
|
2023-10-24 15:42:44 +02:00
|
|
|
private val physicalDevices = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && characteristics.physicalCameraIds.isNotEmpty()) {
|
|
|
|
characteristics.physicalCameraIds
|
|
|
|
} else {
|
|
|
|
setOf(cameraId)
|
|
|
|
}
|
2023-03-13 14:23:19 +01:00
|
|
|
private val minZoom = zoomRange.lower.toDouble()
|
|
|
|
private val maxZoom = zoomRange.upper.toDouble()
|
|
|
|
|
|
|
|
private val cameraConfig = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)!!
|
|
|
|
private val isoRange = characteristics.get(CameraCharacteristics.SENSOR_INFO_SENSITIVITY_RANGE) ?: Range(0, 0)
|
2023-09-21 11:20:33 +02:00
|
|
|
private val digitalStabilizationModes =
|
|
|
|
characteristics.get(CameraCharacteristics.CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES) ?: IntArray(0)
|
|
|
|
private val opticalStabilizationModes =
|
|
|
|
characteristics.get(CameraCharacteristics.LENS_INFO_AVAILABLE_OPTICAL_STABILIZATION) ?: IntArray(0)
|
|
|
|
private val supportsPhotoHdr = extensions.contains(3) // TODO: CameraExtensionCharacteristics.EXTENSION_HDR
|
2023-03-13 14:23:19 +01:00
|
|
|
private val supportsVideoHdr = getHasVideoHdr()
|
|
|
|
|
2023-08-21 12:50:14 +02:00
|
|
|
private val videoFormat = ImageFormat.YUV_420_888
|
2023-03-13 14:23:19 +01:00
|
|
|
|
|
|
|
// get extensions (HDR, Night Mode, ..)
|
2023-09-21 11:20:33 +02:00
|
|
|
private fun getSupportedExtensions(): List<Int> =
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
2023-03-13 14:23:19 +01:00
|
|
|
val extensions = cameraManager.getCameraExtensionCharacteristics(cameraId)
|
|
|
|
extensions.supportedExtensions
|
|
|
|
} else {
|
|
|
|
emptyList()
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun getHasVideoHdr(): Boolean {
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
|
|
if (capabilities.contains(CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT)) {
|
2023-11-15 18:33:12 +01:00
|
|
|
val recommendedHdrProfile = characteristics.get(CameraCharacteristics.REQUEST_RECOMMENDED_TEN_BIT_DYNAMIC_RANGE_PROFILE)
|
|
|
|
return recommendedHdrProfile != null
|
2023-03-13 14:23:19 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun createStabilizationModes(): ReadableArray {
|
|
|
|
val array = Arguments.createArray()
|
2023-08-21 12:50:14 +02:00
|
|
|
digitalStabilizationModes.forEach { videoStabilizationMode ->
|
|
|
|
val mode = VideoStabilizationMode.fromDigitalVideoStabilizationMode(videoStabilizationMode)
|
|
|
|
array.pushString(mode.unionValue)
|
|
|
|
}
|
|
|
|
opticalStabilizationModes.forEach { videoStabilizationMode ->
|
|
|
|
val mode = VideoStabilizationMode.fromOpticalVideoStabilizationMode(videoStabilizationMode)
|
|
|
|
array.pushString(mode.unionValue)
|
2023-03-13 14:23:19 +01:00
|
|
|
}
|
|
|
|
return array
|
|
|
|
}
|
|
|
|
|
2023-10-24 14:27:47 +02:00
|
|
|
private fun getDeviceTypes(): List<DeviceType> {
|
2023-10-24 15:42:44 +02:00
|
|
|
val deviceTypes = physicalDevices.map { id ->
|
|
|
|
val details = CameraDeviceDetails(cameraManager, id)
|
|
|
|
val fov = details.getMaxFieldOfView()
|
2023-10-24 14:27:47 +02:00
|
|
|
return@map when {
|
|
|
|
fov > 94 -> DeviceType.ULTRA_WIDE_ANGLE
|
|
|
|
fov in 60f..94f -> DeviceType.WIDE_ANGLE
|
|
|
|
fov < 60f -> DeviceType.TELEPHOTO
|
2023-10-24 15:42:44 +02:00
|
|
|
else -> throw Error("Invalid Field Of View! ($fov)")
|
2023-09-11 11:33:02 +02:00
|
|
|
}
|
|
|
|
}
|
2023-03-13 14:23:19 +01:00
|
|
|
return deviceTypes
|
|
|
|
}
|
|
|
|
|
2023-10-24 14:27:47 +02:00
|
|
|
private fun getFieldOfView(focalLength: Float): Double {
|
|
|
|
val sensorDiagonal = sqrt((sensorSize.width * sensorSize.width + sensorSize.height * sensorSize.height).toDouble())
|
|
|
|
val fovRadians = 2.0 * atan2(sensorDiagonal, (2.0 * focalLength))
|
|
|
|
return Math.toDegrees(fovRadians)
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun getMaxFieldOfView(): Double {
|
|
|
|
val smallestFocalLength = focalLengths.minOrNull() ?: return 0.0
|
|
|
|
return getFieldOfView(smallestFocalLength)
|
|
|
|
}
|
2023-03-13 14:23:19 +01:00
|
|
|
|
2023-09-21 11:20:33 +02:00
|
|
|
private fun getVideoSizes(): List<Size> = characteristics.getVideoSizes(cameraId, videoFormat)
|
|
|
|
private fun getPhotoSizes(): List<Size> = characteristics.getPhotoSizes(ImageFormat.JPEG)
|
2023-03-13 14:23:19 +01:00
|
|
|
|
|
|
|
private fun getFormats(): ReadableArray {
|
|
|
|
val array = Arguments.createArray()
|
|
|
|
|
2023-08-21 12:50:14 +02:00
|
|
|
val videoSizes = getVideoSizes()
|
|
|
|
val photoSizes = getPhotoSizes()
|
2023-03-13 14:23:19 +01:00
|
|
|
|
2023-08-21 12:50:14 +02:00
|
|
|
videoSizes.forEach { videoSize ->
|
|
|
|
val frameDuration = cameraConfig.getOutputMinFrameDuration(videoFormat, videoSize)
|
|
|
|
val maxFps = (1.0 / (frameDuration.toDouble() / 1_000_000_000)).toInt()
|
2023-03-13 14:23:19 +01:00
|
|
|
|
2023-08-21 12:50:14 +02:00
|
|
|
photoSizes.forEach { photoSize ->
|
|
|
|
val map = buildFormatMap(photoSize, videoSize, Range(1, maxFps))
|
2023-03-13 14:23:19 +01:00
|
|
|
array.pushMap(map)
|
|
|
|
}
|
2023-08-21 12:50:14 +02:00
|
|
|
}
|
2023-03-13 14:23:19 +01:00
|
|
|
|
2023-08-21 12:50:14 +02:00
|
|
|
// TODO: Add high-speed video ranges (high-fps / slow-motion)
|
2023-03-13 14:23:19 +01:00
|
|
|
|
2023-08-21 12:50:14 +02:00
|
|
|
return array
|
|
|
|
}
|
|
|
|
|
2023-09-29 21:52:19 +02:00
|
|
|
private fun createPixelFormats(): ReadableArray {
|
|
|
|
// Every output in Camera2 supports YUV and NATIVE
|
2023-08-21 12:50:14 +02:00
|
|
|
val array = Arguments.createArray()
|
2023-09-29 21:52:19 +02:00
|
|
|
array.pushString(PixelFormat.YUV.unionValue)
|
|
|
|
array.pushString(PixelFormat.NATIVE.unionValue)
|
2023-03-13 14:23:19 +01:00
|
|
|
return array
|
|
|
|
}
|
|
|
|
|
2023-08-21 12:50:14 +02:00
|
|
|
private fun buildFormatMap(photoSize: Size, videoSize: Size, fpsRange: Range<Int>): ReadableMap {
|
|
|
|
val map = Arguments.createMap()
|
|
|
|
map.putInt("photoHeight", photoSize.height)
|
|
|
|
map.putInt("photoWidth", photoSize.width)
|
|
|
|
map.putInt("videoHeight", videoSize.height)
|
|
|
|
map.putInt("videoWidth", videoSize.width)
|
|
|
|
map.putInt("minISO", isoRange.lower)
|
|
|
|
map.putInt("maxISO", isoRange.upper)
|
|
|
|
map.putInt("minFps", fpsRange.lower)
|
|
|
|
map.putInt("maxFps", fpsRange.upper)
|
2023-10-24 11:19:03 +02:00
|
|
|
map.putDouble("maxZoom", maxZoom)
|
2023-10-24 14:27:47 +02:00
|
|
|
map.putDouble("fieldOfView", getMaxFieldOfView())
|
2023-11-15 18:33:12 +01:00
|
|
|
map.putBoolean("supportsVideoHdr", supportsVideoHdr)
|
|
|
|
map.putBoolean("supportsPhotoHdr", supportsPhotoHdr)
|
2023-09-21 16:29:46 +02:00
|
|
|
map.putBoolean("supportsDepthCapture", supportsDepthCapture)
|
2023-10-24 11:19:03 +02:00
|
|
|
map.putString("autoFocusSystem", AutoFocusSystem.CONTRAST_DETECTION.unionValue)
|
2023-08-21 12:50:14 +02:00
|
|
|
map.putArray("videoStabilizationModes", createStabilizationModes())
|
2023-09-29 21:52:19 +02:00
|
|
|
map.putArray("pixelFormats", createPixelFormats())
|
2023-08-21 12:50:14 +02:00
|
|
|
return map
|
|
|
|
}
|
|
|
|
|
2023-03-13 14:23:19 +01:00
|
|
|
fun toMap(): ReadableMap {
|
2023-10-24 14:27:47 +02:00
|
|
|
val deviceTypes = getDeviceTypes()
|
|
|
|
|
2023-03-13 14:23:19 +01:00
|
|
|
val map = Arguments.createMap()
|
|
|
|
map.putString("id", cameraId)
|
2023-10-24 14:27:47 +02:00
|
|
|
map.putArray("physicalDevices", deviceTypes.toJSValue())
|
2023-08-21 12:50:14 +02:00
|
|
|
map.putString("position", lensFacing.unionValue)
|
2023-03-13 14:23:19 +01:00
|
|
|
map.putString("name", name)
|
|
|
|
map.putBoolean("hasFlash", hasFlash)
|
|
|
|
map.putBoolean("hasTorch", hasFlash)
|
|
|
|
map.putBoolean("isMultiCam", isMultiCam)
|
|
|
|
map.putBoolean("supportsRawCapture", supportsRawCapture)
|
|
|
|
map.putBoolean("supportsLowLightBoost", supportsLowLightBoost)
|
|
|
|
map.putBoolean("supportsFocus", true) // I believe every device here supports focussing
|
|
|
|
map.putDouble("minZoom", minZoom)
|
|
|
|
map.putDouble("maxZoom", maxZoom)
|
|
|
|
map.putDouble("neutralZoom", 1.0) // Zoom is always relative to 1.0 on Android
|
2023-08-21 12:50:14 +02:00
|
|
|
map.putString("hardwareLevel", hardwareLevel.unionValue)
|
2023-09-01 15:07:16 +02:00
|
|
|
map.putString("sensorOrientation", Orientation.fromRotationDegrees(sensorOrientation).unionValue)
|
2023-03-13 14:23:19 +01:00
|
|
|
map.putArray("formats", getFormats())
|
|
|
|
return map
|
|
|
|
}
|
|
|
|
}
|