Skip to content

Commit

Permalink
feat: Create persistent CaptureSession to avoid any blackscreen iss…
Browse files Browse the repository at this point in the history
…ues or errors (mrousavy#2494)

* feat: Create custom `CaptureSession` wrapper

* Create `PersistentCameraCaptureSession`

* Update VideoStabilizationMode.kt

* Create RepeatingRequest.kt

* Update CaptureSession.kt

* Delete CaptureSession.kt

* Update PersistentCameraCaptureSession.kt

* Update PersistentCameraCaptureSession.kt

* fix: Add `isRepeating`

* Update CameraSession.kt

* Make `SurfaceOutput` not `Closable` anymore

* Update PersistentCameraCaptureSession.kt

* Stub out the rest

* Format

* Set `isRunning` properly

* Close previous outputs

* onError callback

* Format

* Started/Stopped

* Update CameraPage.tsx

* Add `isValid`

* Log `isActive`

* Add `tryAbortCaptures`

* Configure()

* Try?

* Add `didDestroyFromOutside`

* Disable FP for testing

* fix: Call `super.onAttachedToWindow` first

* Hm

* Update CameraSession.kt

* Update PersistentCameraCaptureSession.kt

* Try catch `didDestroyFromOutside`

* Update PersistentCameraCaptureSession.kt

* Session can only be active with a preview

* Update PersistentCameraCaptureSession.kt

* Throw `no-outputs` if needed

* Update logs

* fix: Check for CAMERA permission

* fix: Close session when opening a new device

* perf: Make everything `by lazy` in CameraDeviceDetails

* Update CameraDeviceDetails.kt

* Update PersistentCameraCaptureSession.kt

* Update PersistentCameraCaptureSession.kt

* Move

* Update Podfile.lock

* Implement `capture()`

* Format

* fix: Fix orientation not being applied

* fix: Fix `isMirrored`

* fix: Fix getting size

* fix: Close `Surface` in `VideoPipeline`

* Format

* fix: Fix `VideoPipeline` not properly destroying itself

* Use FP again

* Update CameraConfiguration.kt

* Rename

* Clean up

* Format

* Update CameraConfiguration.kt

* fix: Don't stop repeating request when capturing
  • Loading branch information
mrousavy authored Feb 6, 2024
1 parent 502e1aa commit d0926a9
Show file tree
Hide file tree
Showing 20 changed files with 749 additions and 499 deletions.
3 changes: 2 additions & 1 deletion package/android/src/main/cpp/OpenGLRenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,11 @@ OpenGLRenderer::OpenGLRenderer(std::shared_ptr<OpenGLContext> context, ANativeWi
}

OpenGLRenderer::~OpenGLRenderer() {
__android_log_print(ANDROID_LOG_INFO, TAG, "Destroying OpenGLRenderer...");
destroy();
if (_outputSurface != nullptr) {
ANativeWindow_release(_outputSurface);
}
destroy();
}

void OpenGLRenderer::destroy() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,17 +113,17 @@ class CameraView(context: Context) :
}

override fun onAttachedToWindow() {
super.onAttachedToWindow()
if (!isMounted) {
isMounted = true
invokeOnViewReady()
}
update()
super.onAttachedToWindow()
}

override fun onDetachedFromWindow() {
update()
super.onDetachedFromWindow()
update()
}

fun destroy() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,22 +67,25 @@ data class CameraConfiguration(
}

data class Difference(
// Input Camera (cameraId, isActive)
// Input Camera (cameraId)
val deviceChanged: Boolean,
// Outputs & Session (Photo, Video, CodeScanner, HDR, Format)
val outputsChanged: Boolean,
// Side-Props for CaptureRequest (fps, low-light-boost, torch, zoom, videoStabilization)
val sidePropsChanged: Boolean,
// (isActive) changed
val isActiveChanged: Boolean
)
) {
val hasChanges: Boolean
get() = deviceChanged || outputsChanged || sidePropsChanged || isActiveChanged
}

companion object {
fun copyOf(other: CameraConfiguration?): CameraConfiguration = other?.copy() ?: CameraConfiguration()

fun difference(left: CameraConfiguration?, right: CameraConfiguration): Difference {
// input device
val deviceChanged = left?.cameraId != right.cameraId || left?.isActive != right.isActive
val deviceChanged = left?.cameraId != right.cameraId

// outputs
val outputsChanged = deviceChanged ||
Expand All @@ -101,7 +104,7 @@ data class CameraConfiguration(
left.videoStabilizationMode != right.videoStabilizationMode ||
left.exposure != right.exposure

val isActiveChanged = left?.isActive != right.isActive
val isActiveChanged = sidePropsChanged || left?.isActive != right.isActive

return Difference(
deviceChanged,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.mrousavy.camera.core

import android.annotation.SuppressLint
import android.graphics.ImageFormat
import android.hardware.camera2.CameraCharacteristics
import android.hardware.camera2.CameraExtensionCharacteristics
import android.hardware.camera2.CameraManager
import android.hardware.camera2.CameraMetadata
import android.os.Build
Expand All @@ -23,61 +25,73 @@ import com.mrousavy.camera.types.VideoStabilizationMode
import kotlin.math.atan2
import kotlin.math.sqrt

class CameraDeviceDetails(val cameraManager: CameraManager, val cameraId: String) {
val characteristics = cameraManager.getCameraCharacteristics(cameraId)
val hardwareLevel = HardwareLevel.fromCameraCharacteristics(characteristics)
val capabilities = characteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES) ?: IntArray(0)
val extensions = getSupportedExtensions()
@SuppressLint("InlinedApi")
class CameraDeviceDetails(private val cameraManager: CameraManager, val cameraId: String) {
val characteristics by lazy { cameraManager.getCameraCharacteristics(cameraId) }
val hardwareLevel by lazy { HardwareLevel.fromCameraCharacteristics(characteristics) }
val capabilities by lazy { characteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES) ?: IntArray(0) }
val extensions by lazy { getSupportedExtensions() }

// device characteristics
val isMultiCam = capabilities.contains(11) // TODO: CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA
val supportsDepthCapture = capabilities.contains(8) // TODO: CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_DEPTH_OUTPUT
val supportsRawCapture = capabilities.contains(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW)
val supportsLowLightBoost = extensions.contains(4) // TODO: CameraExtensionCharacteristics.EXTENSION_NIGHT
val lensFacing = LensFacing.fromCameraCharacteristics(characteristics)
val hasFlash = characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE) ?: false
val focalLengths =
characteristics.get(CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS)
// 35mm is the film standard sensor size
?: floatArrayOf(35f)
val sensorSize = characteristics.get(CameraCharacteristics.SENSOR_INFO_PHYSICAL_SIZE)!!
val sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION)!!
val minFocusDistance = getMinFocusDistanceCm()
val name = (
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
characteristics.get(CameraCharacteristics.INFO_VERSION)
} else {
null
}
) ?: "$lensFacing ($cameraId)"
val isMultiCam by lazy { capabilities.contains(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA) }
val supportsDepthCapture by lazy { capabilities.contains(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_DEPTH_OUTPUT) }
val supportsRawCapture by lazy { capabilities.contains(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW) }
val supportsLowLightBoost by lazy { extensions.contains(CameraExtensionCharacteristics.EXTENSION_NIGHT) }
val lensFacing by lazy { LensFacing.fromCameraCharacteristics(characteristics) }
val hasFlash by lazy { characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE) ?: false }
val focalLengths by lazy {
// 35mm is the film standard sensor size
characteristics.get(CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS) ?: floatArrayOf(35f)
}
val sensorSize by lazy { characteristics.get(CameraCharacteristics.SENSOR_INFO_PHYSICAL_SIZE)!! }
val sensorOrientation by lazy { characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION) ?: 0 }
val minFocusDistance by lazy { getMinFocusDistanceCm() }
val name by lazy {
val info = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) characteristics.get(CameraCharacteristics.INFO_VERSION) else null
return@lazy info ?: "$lensFacing ($cameraId)"
}

// "formats" (all possible configurations for this device)
val zoomRange = (
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val maxDigitalZoom by lazy { characteristics.get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM) ?: 1f }
val zoomRange by lazy {
val range = 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)
val physicalDevices = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && characteristics.physicalCameraIds.isNotEmpty()) {
characteristics.physicalCameraIds
} else {
setOf(cameraId)
}
val minZoom = zoomRange.lower.toDouble()
val maxZoom = zoomRange.upper.toDouble()

val cameraConfig = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)!!
val isoRange = characteristics.get(CameraCharacteristics.SENSOR_INFO_SENSITIVITY_RANGE) ?: Range(0, 0)
val exposureRange = characteristics.get(CameraCharacteristics.CONTROL_AE_COMPENSATION_RANGE) ?: Range(0, 0)
val digitalStabilizationModes =
return@lazy range ?: Range(1f, maxDigitalZoom)
}
val physicalDevices by lazy {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && characteristics.physicalCameraIds.isNotEmpty()) {
characteristics.physicalCameraIds
} else {
setOf(cameraId)
}
}
val minZoom by lazy { zoomRange.lower.toDouble() }
val maxZoom by lazy { zoomRange.upper.toDouble() }

val cameraConfig by lazy { characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)!! }
val isoRange by lazy { characteristics.get(CameraCharacteristics.SENSOR_INFO_SENSITIVITY_RANGE) ?: Range(0, 0) }
val exposureRange by lazy { characteristics.get(CameraCharacteristics.CONTROL_AE_COMPENSATION_RANGE) ?: Range(0, 0) }
val digitalStabilizationModes by lazy {
characteristics.get(CameraCharacteristics.CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES) ?: IntArray(0)
val opticalStabilizationModes =
}
val opticalStabilizationModes by lazy {
characteristics.get(CameraCharacteristics.LENS_INFO_AVAILABLE_OPTICAL_STABILIZATION) ?: IntArray(0)
val supportsPhotoHdr = extensions.contains(3) // TODO: CameraExtensionCharacteristics.EXTENSION_HDR
val supportsVideoHdr = getHasVideoHdr()
val autoFocusSystem = getAutoFocusSystemMode()
}
val supportsPhotoHdr by lazy { extensions.contains(CameraExtensionCharacteristics.EXTENSION_HDR) }
val supportsVideoHdr by lazy { getHasVideoHdr() }
val autoFocusSystem by lazy { getAutoFocusSystemMode() }

val supportsYuvProcessing by lazy { capabilities.contains(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_YUV_REPROCESSING) }
val supportsPrivateProcessing by lazy { capabilities.contains(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING) }
val supportsZsl by lazy { supportsYuvProcessing || supportsPrivateProcessing }

val isBackwardsCompatible by lazy { capabilities.contains(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE) }
val supportsSnapshotCapture by lazy { supportsSnapshotCapture() }

// TODO: Also add 10-bit YUV here?
val videoFormat = ImageFormat.YUV_420_888

// get extensions (HDR, Night Mode, ..)
Expand Down Expand Up @@ -107,6 +121,14 @@ class CameraDeviceDetails(val cameraManager: CameraManager, val cameraId: String
return 1.0 / distance * 100.0
}

@Suppress("RedundantIf")
private fun supportsSnapshotCapture(): Boolean {
// As per CameraDevice.TEMPLATE_VIDEO_SNAPSHOT in documentation:
if (hardwareLevel == HardwareLevel.LEGACY) return false
if (supportsDepthCapture && !isBackwardsCompatible) return false
return true
}

private fun createStabilizationModes(): ReadableArray {
val array = Arguments.createArray()
digitalStabilizationModes.forEach { videoStabilizationMode ->
Expand Down Expand Up @@ -176,8 +198,6 @@ class CameraDeviceDetails(val cameraManager: CameraManager, val cameraId: String
}
}

// TODO: Add high-speed video ranges (high-fps / slow-motion)

return array
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ class CameraSessionCannotBeConfiguredError(cameraId: String) :
CameraError("session", "cannot-create-session", "Failed to create a Camera Session for Camera #$cameraId!")
class CameraDisconnectedError(cameraId: String, error: CameraDeviceError) :
CameraError("session", "camera-has-been-disconnected", "The given Camera device (id: $cameraId) has been disconnected! Error: $error")
class NoOutputsError :
CameraError("session", "no-outputs", "Cannot create a CameraCaptureSession without any outputs! (PREVIEW, PHOTO, VIDEO, ...)")

class PropRequiresFormatToBeNonNullError(propName: String) :
CameraError("format", "format-required", "The prop \"$propName\" requires a format to be set, but format was null!")
Expand Down
Loading

0 comments on commit d0926a9

Please sign in to comment.