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

Zoom Refactor #293

Open
wants to merge 18 commits into
base: main
Choose a base branch
from
Open
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 @@ -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.CameraZoomState
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 @@ -98,9 +99,11 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch

Expand All @@ -117,7 +120,8 @@ internal suspend fun runSingleCameraSession(
sessionSettings: PerpetualSessionSettings.SingleCamera,
useCaseMode: CameraUseCase.UseCaseMode,
// TODO(tm): ImageCapture should go through an event channel like VideoCapture
onImageCaptureCreated: (ImageCapture) -> Unit = {}
onImageCaptureCreated: (ImageCapture) -> Unit = {},
onSetZoomRatioMap: (Map<LensFacing, Float>) -> Unit = { _ -> }
) = coroutineScope {
Log.d(TAG, "Starting new single camera session")

Expand Down Expand Up @@ -215,6 +219,85 @@ internal suspend fun runSingleCameraSession(
}
}

// update camerastate to mirror current zoomstate
launch {
/*
TODO bug?? Flaky behavior here. does not always update zoomstate properly when:
switching HDR on or off on front lens
Setting aspect ratio on either lens...
basically anything that restarts the session
*/
camera.cameraInfo.zoomState.asFlow()
.filterNotNull()
.distinctUntilChanged()
.onCompletion {
// reset current camera state when changing cameras.
currentCameraState.update { old ->
old.copy(
zoomRatios = emptyMap(),
linearZoomScales = emptyMap()
)
}
}
.collectLatest { zoomState ->
currentCameraState.update { old ->
old.copy(
zoomRatios = old.zoomRatios.toMutableMap().apply {
put(camera.cameraInfo.appLensFacing, zoomState.zoomRatio)
}.toMap(),
linearZoomScales = old.linearZoomScales.toMutableMap().apply {
put(camera.cameraInfo.appLensFacing, zoomState.linearZoom)
}.toMap()
)
}
// update current settings to mirror current camera state
onSetZoomRatioMap(
currentCameraState.value.zoomRatios
)
}
}

launch {
// Immediately Apply camera zoom from current settings when opening a new camera
camera.cameraControl.setZoomRatio(
currentTransientSettings.zoomRatios[camera.cameraInfo.appLensFacing] ?: 1f
)
Log.d(
TAG,
"Starting camera ${camera.cameraInfo.appLensFacing} at zoom ratio " +
"${camera.cameraInfo.zoomState.value?.zoomRatio}"
)

// Apply zoom changes to camera
zoomChanges.drop(1).filterNotNull().collectLatest { zoomChange ->
val currentZoomState = camera.cameraInfo.zoomState
.asFlow().filterNotNull().first()
when (zoomChange) {
is CameraZoomState.Ratio -> {
camera.cameraControl.setZoomRatio(
zoomChange.value.coerceIn(
currentZoomState.minZoomRatio,
currentZoomState.maxZoomRatio
)
)
}

is CameraZoomState.Linear -> {
camera.cameraControl.setLinearZoom(zoomChange.value)
}

is CameraZoomState.Scale -> {
val newRatio =
(currentZoomState.zoomRatio * zoomChange.value).coerceIn(
currentZoomState.minZoomRatio,
currentZoomState.maxZoomRatio
)
camera.cameraControl.setZoomRatio(newRatio)
}
}
}
}

applyDeviceRotation(currentTransientSettings.deviceRotation, useCaseGroup)
processTransientSettingEvents(
camera,
Expand Down Expand Up @@ -252,21 +335,6 @@ internal suspend fun processTransientSettingEvents(
val newTransientSettings = it.first
val cameraState = it.second

// Apply camera zoom
if (prevTransientSettings.zoomScale != newTransientSettings.zoomScale) {
camera.cameraInfo.zoomState.value?.let { zoomState ->
val finalScale =
(zoomState.zoomRatio * newTransientSettings.zoomScale).coerceIn(
zoomState.minZoomRatio,
zoomState.maxZoomRatio
)
camera.cameraControl.setZoomRatio(finalScale)
currentCameraState.update { old ->
old.copy(zoomScale = finalScale)
}
}
}

// todo(): How should we handle torch on Auto FlashMode?
// enable torch only while recording is in progress
if ((cameraState.videoRecordingState !is VideoRecordingState.Inactive) &&
Expand Down Expand Up @@ -414,13 +482,12 @@ internal fun createUseCaseGroup(
}.build()
}

private fun getVideoQualityFromResolution(resolution: Size?): VideoQuality {
return resolution?.let { res ->
private fun getVideoQualityFromResolution(resolution: Size?): VideoQuality =
resolution?.let { res ->
QUALITY_RANGE_MAP.firstNotNullOfOrNull {
if (it.value.contains(res.height)) it.key else null
}
} ?: VideoQuality.UNSPECIFIED
}

private fun getWidthFromCropRect(cropRect: Rect?): Int {
if (cropRect == null) {
Expand Down Expand Up @@ -698,7 +765,8 @@ private suspend fun startVideoRecordingInternal(
context: Context,
pendingRecord: PendingRecording,
maxDurationMillis: Long,
onVideoRecord: (CameraUseCase.OnVideoRecordEvent) -> Unit
onVideoRecord: (CameraUseCase.OnVideoRecordEvent) -> Unit,
onRestoreSettings: () -> Unit = {}
): Recording {
Log.d(TAG, "recordVideo")
// todo(b/336886716): default setting to enable or disable audio when permission is granted
Expand Down Expand Up @@ -819,6 +887,7 @@ private suspend fun startVideoRecordingInternal(
onVideoRecordEvent.outputResults.outputUri
)
)
onRestoreSettings()
}

ERROR_DURATION_LIMIT_REACHED -> {
Expand Down Expand Up @@ -874,7 +943,8 @@ private suspend fun runVideoRecording(
videoCaptureUri: Uri?,
videoControlEvents: Channel<VideoCaptureControlEvent>,
shouldUseUri: Boolean,
onVideoRecord: (CameraUseCase.OnVideoRecordEvent) -> Unit
onVideoRecord: (CameraUseCase.OnVideoRecordEvent) -> Unit,
onRestoreSettings: () -> Unit = {}
) = coroutineScope {
var currentSettings = transientSettings.filterNotNull().first()

Expand All @@ -892,7 +962,8 @@ private suspend fun runVideoRecording(
context = context,
pendingRecord = it,
maxDurationMillis = maxDurationMillis,
onVideoRecord = onVideoRecord
onVideoRecord = onVideoRecord,
onRestoreSettings = onRestoreSettings
).use { recording ->
val recordingSettingsUpdater = launch {
fun TransientSessionSettings.isFlashModeOn() = flashMode == FlashMode.ON
Expand Down Expand Up @@ -975,7 +1046,8 @@ internal suspend fun processVideoControlEvents(
event.videoCaptureUri,
videoCaptureControlEvents,
event.shouldUseUri,
event.onVideoRecord
event.onVideoRecord,
event.onRestoreSettings
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package com.google.jetpackcamera.core.camera
import android.content.Context
import androidx.camera.core.SurfaceRequest
import androidx.camera.lifecycle.ProcessCameraProvider
import com.google.jetpackcamera.settings.model.CameraZoomState
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.SendChannel
Expand All @@ -37,6 +38,7 @@ internal data class CameraSessionContext(
val screenFlashEvents: SendChannel<CameraUseCase.ScreenFlashEvent>,
val focusMeteringEvents: Channel<CameraEvent.FocusMeteringEvent>,
val videoCaptureControlEvents: Channel<VideoCaptureControlEvent>,
val zoomChanges: StateFlow<CameraZoomState?>,
val currentCameraState: MutableStateFlow<CameraState>,
val surfaceRequests: MutableStateFlow<SurfaceRequest?>,
val transientSettings: StateFlow<TransientSessionSettings?>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,6 @@ internal data class TransientSessionSettings(
val isAudioEnabled: Boolean,
val deviceRotation: DeviceRotation,
val flashMode: FlashMode,
val zoomScale: Float,
val primaryLensFacing: LensFacing
val primaryLensFacing: LensFacing,
val zoomRatios: Map<LensFacing, Float>
)
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.CameraZoomState
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 @@ -86,7 +87,7 @@ interface CameraUseCase {

suspend fun stopVideoRecording()

fun setZoomScale(scale: Float)
fun changeZoom(newZoomState: CameraZoomState)

fun getCurrentCameraState(): StateFlow<CameraState>

Expand Down Expand Up @@ -190,7 +191,8 @@ sealed interface VideoRecordingState {

data class CameraState(
val videoRecordingState: VideoRecordingState = VideoRecordingState.Inactive(),
val zoomScale: Float = 1f,
val zoomRatios: Map<LensFacing, Float> = mapOf(),
val linearZoomScales: Map<LensFacing, Float> = mapOf(),
val sessionFirstFrameTimestamp: Long = 0L,
val torchEnabled: Boolean = false,
val stabilizationMode: StabilizationMode = StabilizationMode.OFF,
Expand Down
Loading
Loading