Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: capture mode UI and UiState #303

Draft
wants to merge 16 commits into
base: kim/refactor/captureModes/update_useCaseMode_to_captureMode
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ class CameraXCameraUseCaseTest {
iODispatcher = Dispatchers.IO,
constraintsRepository = constraintsRepository
).apply {
initialize(appSettings, CameraUseCase.UseCaseMode.STANDARD) {}
initialize(appSettings) {}
providePreviewSurface()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ import androidx.core.content.ContextCompat.checkSelfPermission
import androidx.lifecycle.asFlow
import com.google.jetpackcamera.core.camera.effects.SingleSurfaceForcingEffect
import com.google.jetpackcamera.settings.model.AspectRatio
import com.google.jetpackcamera.settings.model.CaptureMode
import com.google.jetpackcamera.settings.model.DeviceRotation
import com.google.jetpackcamera.settings.model.DynamicRange
import com.google.jetpackcamera.settings.model.FlashMode
Expand Down Expand Up @@ -115,7 +116,6 @@ private val QUALITY_RANGE_MAP = mapOf(
context(CameraSessionContext)
internal suspend fun runSingleCameraSession(
sessionSettings: PerpetualSessionSettings.SingleCamera,
useCaseMode: CameraUseCase.UseCaseMode,
// TODO(tm): ImageCapture should go through an event channel like VideoCapture
onImageCaptureCreated: (ImageCapture) -> Unit = {}
) = coroutineScope {
Expand All @@ -124,8 +124,8 @@ internal suspend fun runSingleCameraSession(
val initialCameraSelector = transientSettings.filterNotNull().first()
.primaryLensFacing.toCameraSelector()

val videoCaptureUseCase = when (useCaseMode) {
CameraUseCase.UseCaseMode.STANDARD, CameraUseCase.UseCaseMode.VIDEO_ONLY ->
val videoCaptureUseCase = when (sessionSettings.captureMode) {
CaptureMode.STANDARD, CaptureMode.VIDEO_ONLY ->
createVideoUseCase(
cameraProvider.getCameraInfo(initialCameraSelector),
sessionSettings.aspectRatio,
Expand All @@ -135,7 +135,6 @@ internal suspend fun runSingleCameraSession(
sessionSettings.videoQuality,
backgroundDispatcher
)

else -> {
null
}
Expand Down Expand Up @@ -164,7 +163,7 @@ internal suspend fun runSingleCameraSession(
aspectRatio = sessionSettings.aspectRatio,
dynamicRange = sessionSettings.dynamicRange,
imageFormat = sessionSettings.imageFormat,
useCaseMode = useCaseMode,
captureMode = sessionSettings.captureMode,
effect = when (sessionSettings.streamConfig) {
StreamConfig.SINGLE_STREAM -> SingleSurfaceForcingEffect(this@coroutineScope)
StreamConfig.MULTI_STREAM -> null
Expand Down Expand Up @@ -359,7 +358,7 @@ internal fun createUseCaseGroup(
videoCaptureUseCase: VideoCapture<Recorder>?,
dynamicRange: DynamicRange,
imageFormat: ImageOutputFormat,
useCaseMode: CameraUseCase.UseCaseMode,
captureMode: CaptureMode,
effect: CameraEffect? = null
): UseCaseGroup {
val previewUseCase =
Expand All @@ -368,7 +367,7 @@ internal fun createUseCaseGroup(
aspectRatio,
stabilizationMode
)
val imageCaptureUseCase = if (useCaseMode != CameraUseCase.UseCaseMode.VIDEO_ONLY) {
val imageCaptureUseCase = if (captureMode != CaptureMode.VIDEO_ONLY) {
createImageUseCase(cameraInfo, aspectRatio, dynamicRange, imageFormat)
} else {
null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package com.google.jetpackcamera.core.camera

import androidx.camera.core.CameraInfo
import com.google.jetpackcamera.settings.model.AspectRatio
import com.google.jetpackcamera.settings.model.CaptureMode
import com.google.jetpackcamera.settings.model.DeviceRotation
import com.google.jetpackcamera.settings.model.DynamicRange
import com.google.jetpackcamera.settings.model.FlashMode
Expand All @@ -34,9 +35,11 @@ import com.google.jetpackcamera.settings.model.VideoQuality
*/
internal sealed interface PerpetualSessionSettings {
val aspectRatio: AspectRatio
val captureMode: CaptureMode

data class SingleCamera(
override val aspectRatio: AspectRatio,
override val captureMode: CaptureMode,
val streamConfig: StreamConfig,
val targetFrameRate: Int,
val stabilizationMode: StabilizationMode,
Expand All @@ -49,7 +52,9 @@ internal sealed interface PerpetualSessionSettings {
val primaryCameraInfo: CameraInfo,
val secondaryCameraInfo: CameraInfo,
override val aspectRatio: AspectRatio
) : PerpetualSessionSettings
) : PerpetualSessionSettings {
override val captureMode: CaptureMode = CaptureMode.VIDEO_ONLY
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import androidx.camera.core.ImageCapture
import androidx.camera.core.SurfaceRequest
import com.google.jetpackcamera.settings.model.AspectRatio
import com.google.jetpackcamera.settings.model.CameraAppSettings
import com.google.jetpackcamera.settings.model.CaptureMode
import com.google.jetpackcamera.settings.model.ConcurrentCameraMode
import com.google.jetpackcamera.settings.model.DeviceRotation
import com.google.jetpackcamera.settings.model.DynamicRange
Expand All @@ -45,7 +46,6 @@ interface CameraUseCase {
*/
suspend fun initialize(
cameraAppSettings: CameraAppSettings,
useCaseMode: UseCaseMode,
isDebugMode: Boolean = false,
cameraPropertiesJSONCallback: (result: String) -> Unit
)
Expand Down Expand Up @@ -125,6 +125,7 @@ interface CameraUseCase {
suspend fun setTargetFrameRate(targetFrameRate: Int)

suspend fun setMaxVideoDuration(durationInMillis: Long)
suspend fun setCaptureMode(captureMode: CaptureMode)

/**
* Represents the events required for screen flash.
Expand All @@ -145,12 +146,6 @@ interface CameraUseCase {

data class OnVideoRecordError(val error: Throwable) : OnVideoRecordEvent
}

enum class UseCaseMode {
STANDARD,
IMAGE_ONLY,
VIDEO_ONLY
}
}

sealed interface VideoRecordingState {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import com.google.jetpackcamera.settings.model.CameraAppSettings
import com.google.jetpackcamera.settings.model.CameraConstraints
import com.google.jetpackcamera.settings.model.CameraConstraints.Companion.FPS_15
import com.google.jetpackcamera.settings.model.CameraConstraints.Companion.FPS_60
import com.google.jetpackcamera.settings.model.CaptureMode
import com.google.jetpackcamera.settings.model.ConcurrentCameraMode
import com.google.jetpackcamera.settings.model.DeviceRotation
import com.google.jetpackcamera.settings.model.DynamicRange
Expand All @@ -62,7 +63,6 @@ import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
import javax.inject.Inject
import kotlin.properties.Delegates
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.coroutineScope
Expand Down Expand Up @@ -101,7 +101,6 @@ constructor(
private var imageCaptureUseCase: ImageCapture? = null

private lateinit var systemConstraints: SystemConstraints
private var useCaseMode by Delegates.notNull<CameraUseCase.UseCaseMode>()

private val screenFlashEvents: Channel<CameraUseCase.ScreenFlashEvent> =
Channel(capacity = Channel.UNLIMITED)
Expand All @@ -121,11 +120,9 @@ constructor(

override suspend fun initialize(
cameraAppSettings: CameraAppSettings,
useCaseMode: CameraUseCase.UseCaseMode,
isDebugMode: Boolean,
cameraPropertiesJSONCallback: (result: String) -> Unit
) {
this.useCaseMode = useCaseMode
cameraProvider = ProcessCameraProvider.awaitInstance(application)

// updates values for available cameras
Expand Down Expand Up @@ -258,12 +255,13 @@ constructor(
currentSettings.value =
cameraAppSettings
.tryApplyDynamicRangeConstraints()
.tryApplyAspectRatioForExternalCapture(this.useCaseMode)
.tryApplyAspectRatioForExternalCapture(cameraAppSettings.captureMode)
.tryApplyImageFormatConstraints()
.tryApplyFrameRateConstraints()
.tryApplyStabilizationConstraints()
.tryApplyConcurrentCameraModeConstraints()
.tryApplyFlashModeConstraints()
.tryApplyCaptureModeConstraints()
.tryApplyVideoQualityConstraints()
if (isDebugMode && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
withContext(iODispatcher) {
Expand Down Expand Up @@ -314,6 +312,7 @@ constructor(

PerpetualSessionSettings.SingleCamera(
aspectRatio = currentCameraSettings.aspectRatio,
captureMode = currentCameraSettings.captureMode,
streamConfig = currentCameraSettings.streamConfig,
targetFrameRate = currentCameraSettings.targetFrameRate,
stabilizationMode = resolvedStabilizationMode,
Expand Down Expand Up @@ -369,16 +368,14 @@ constructor(
when (sessionSettings) {
is PerpetualSessionSettings.SingleCamera -> runSingleCameraSession(
sessionSettings,
useCaseMode = useCaseMode,
onImageCaptureCreated = { imageCapture ->
imageCaptureUseCase = imageCapture
}
)

is PerpetualSessionSettings.ConcurrentCamera ->
runConcurrentCameraSession(
sessionSettings,
useCaseMode = CameraUseCase.UseCaseMode.VIDEO_ONLY
sessionSettings
)
}
} finally {
Expand Down Expand Up @@ -565,12 +562,49 @@ constructor(
?.tryApplyDynamicRangeConstraints()
?.tryApplyImageFormatConstraints()
?.tryApplyFlashModeConstraints()
?.tryApplyCaptureModeConstraints()
} else {
old
}
}
}

private fun CameraAppSettings.tryApplyCaptureModeConstraints(): CameraAppSettings {
Log.d(TAG, "applying capture mode constraints")
systemConstraints.perLensConstraints[cameraLensFacing]?.let { constraints ->
val newCaptureMode =
if (concurrentCameraMode == ConcurrentCameraMode.DUAL) {
Log.d(TAG, "CONCURRENT CAMERA CAPTURE MODE")
CaptureMode.VIDEO_ONLY
} else if (dynamicRange == DynamicRange.HLG10 ||
imageFormat == ImageOutputFormat.JPEG_ULTRA_HDR
) {
if (constraints.supportedDynamicRanges.contains(DynamicRange.HLG10)) {
if (constraints.supportedImageFormatsMap[streamConfig]
?.contains(ImageOutputFormat.JPEG_ULTRA_HDR) == true
) {
// if both image/video are supported, we don't need to change our current capture mode
this.captureMode
} else {
// if only video is supported, change to video only
CaptureMode.VIDEO_ONLY
}
} else {
// if only image is supported, change to image only
CaptureMode.IMAGE_ONLY
}
} else {
// if no dynamic range value is set, its OK to return the current value
return this
}
Log.d(TAG, "new capture mode $newCaptureMode")
return this@tryApplyCaptureModeConstraints.copy(
captureMode = newCaptureMode
)
}
?: return this
}

private fun CameraAppSettings.tryApplyDynamicRangeConstraints(): CameraAppSettings =
systemConstraints.perLensConstraints[cameraLensFacing]?.let { constraints ->
with(constraints.supportedDynamicRanges) {
Expand All @@ -587,13 +621,12 @@ constructor(
} ?: this

private fun CameraAppSettings.tryApplyAspectRatioForExternalCapture(
useCaseMode: CameraUseCase.UseCaseMode
): CameraAppSettings = when (useCaseMode) {
CameraUseCase.UseCaseMode.STANDARD -> this
CameraUseCase.UseCaseMode.IMAGE_ONLY ->
captureMode: CaptureMode
): CameraAppSettings = when (captureMode) {
CaptureMode.STANDARD -> this
CaptureMode.IMAGE_ONLY ->
this.copy(aspectRatio = AspectRatio.THREE_FOUR)

CameraUseCase.UseCaseMode.VIDEO_ONLY ->
CaptureMode.VIDEO_ONLY ->
this.copy(aspectRatio = AspectRatio.NINE_SIXTEEN)
}

Expand Down Expand Up @@ -730,6 +763,7 @@ constructor(
old?.copy(streamConfig = streamConfig)
?.tryApplyImageFormatConstraints()
?.tryApplyConcurrentCameraModeConstraints()
?.tryApplyCaptureModeConstraints()
?.tryApplyVideoQualityConstraints()
}
}
Expand All @@ -738,6 +772,7 @@ constructor(
currentSettings.update { old ->
old?.copy(dynamicRange = dynamicRange)
?.tryApplyConcurrentCameraModeConstraints()
?.tryApplyCaptureModeConstraints()
}
}

Expand All @@ -751,12 +786,14 @@ constructor(
currentSettings.update { old ->
old?.copy(concurrentCameraMode = concurrentCameraMode)
?.tryApplyConcurrentCameraModeConstraints()
?.tryApplyCaptureModeConstraints()
}
}

override suspend fun setImageFormat(imageFormat: ImageOutputFormat) {
currentSettings.update { old ->
old?.copy(imageFormat = imageFormat)
?.tryApplyCaptureModeConstraints()
}
}

Expand All @@ -768,6 +805,10 @@ constructor(
}
}

override suspend fun setCaptureMode(captureMode: CaptureMode) {
currentSettings.update { old -> old?.copy(captureMode = captureMode) }
}

override suspend fun setStabilizationMode(stabilizationMode: StabilizationMode) {
currentSettings.update { old ->
old?.copy(stabilizationMode = stabilizationMode)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import android.util.Log
import androidx.camera.core.CompositionSettings
import androidx.camera.core.TorchState
import androidx.lifecycle.asFlow
import com.google.jetpackcamera.settings.model.CaptureMode
import com.google.jetpackcamera.settings.model.DynamicRange
import com.google.jetpackcamera.settings.model.ImageOutputFormat
import com.google.jetpackcamera.settings.model.StabilizationMode
Expand All @@ -36,8 +37,7 @@ private const val TAG = "ConcurrentCameraSession"
context(CameraSessionContext)
@SuppressLint("RestrictedApi")
internal suspend fun runConcurrentCameraSession(
sessionSettings: PerpetualSessionSettings.ConcurrentCamera,
useCaseMode: CameraUseCase.UseCaseMode
sessionSettings: PerpetualSessionSettings.ConcurrentCamera
) = coroutineScope {
val primaryLensFacing = sessionSettings.primaryCameraInfo.appLensFacing
val secondaryLensFacing = sessionSettings.secondaryCameraInfo.appLensFacing
Expand All @@ -51,7 +51,7 @@ internal suspend fun runConcurrentCameraSession(
.filterNotNull()
.first()

val videoCapture = if (useCaseMode != CameraUseCase.UseCaseMode.IMAGE_ONLY) {
val videoCapture = if (sessionSettings.captureMode != CaptureMode.IMAGE_ONLY) {
createVideoUseCase(
cameraProvider.getCameraInfo(
initialTransientSettings.primaryLensFacing.toCameraSelector()
Expand All @@ -74,7 +74,7 @@ internal suspend fun runConcurrentCameraSession(
aspectRatio = sessionSettings.aspectRatio,
dynamicRange = DynamicRange.SDR,
imageFormat = ImageOutputFormat.JPEG,
useCaseMode = useCaseMode,
captureMode = sessionSettings.captureMode,
videoCaptureUseCase = videoCapture
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import com.google.jetpackcamera.core.camera.CameraState
import com.google.jetpackcamera.core.camera.CameraUseCase
import com.google.jetpackcamera.settings.model.AspectRatio
import com.google.jetpackcamera.settings.model.CameraAppSettings
import com.google.jetpackcamera.settings.model.CaptureMode
import com.google.jetpackcamera.settings.model.ConcurrentCameraMode
import com.google.jetpackcamera.settings.model.DeviceRotation
import com.google.jetpackcamera.settings.model.DynamicRange
Expand Down Expand Up @@ -63,7 +64,6 @@ class FakeCameraUseCase(defaultCameraSettings: CameraAppSettings = CameraAppSett

override suspend fun initialize(
cameraAppSettings: CameraAppSettings,
useCaseMode: CameraUseCase.UseCaseMode,
isDebugMode: Boolean,
cameraPropertiesJSONCallback: (result: String) -> Unit
) {
Expand Down Expand Up @@ -248,4 +248,10 @@ class FakeCameraUseCase(defaultCameraSettings: CameraAppSettings = CameraAppSett
old.copy(maxVideoDurationMillis = durationInMillis)
}
}

override suspend fun setCaptureMode(captureMode: CaptureMode) {
currentSettings.update { old ->
old.copy(captureMode = captureMode)
}
}
}
Loading
Loading