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

Kim/stabilization UI logic #94

Merged
merged 23 commits into from
Jan 31, 2024
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
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 @@ -33,6 +33,8 @@ object JcaSettingsSerializer : Serializer<JcaSettings> {
.setCaptureModeStatus(CaptureMode.CAPTURE_MODE_MULTI_STREAM)
.setStabilizePreview(PreviewStabilization.PREVIEW_STABILIZATION_UNDEFINED)
.setStabilizeVideo(VideoStabilization.VIDEO_STABILIZATION_UNDEFINED)
.setStabilizePreviewSupported(false)
.setStabilizeVideoSupported(false)
.build()

override suspend fun readFrom(input: InputStream): JcaSettings {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import com.google.jetpackcamera.settings.model.CaptureMode
import com.google.jetpackcamera.settings.model.DarkMode
import com.google.jetpackcamera.settings.model.FlashMode
import com.google.jetpackcamera.settings.model.Stabilization
import com.google.jetpackcamera.settings.model.SupportedStabilizationMode
import javax.inject.Inject
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
Expand Down Expand Up @@ -60,6 +61,10 @@ class LocalSettingsRepository @Inject constructor(
aspectRatio = AspectRatio.fromProto(it.aspectRatioStatus),
previewStabilization = Stabilization.fromProto(it.stabilizePreview),
videoCaptureStabilization = Stabilization.fromProto(it.stabilizeVideo),
supportedStabilizationMode = getSupportedStabilization(
previewSupport = it.stabilizePreviewSupported,
videoSupport = it.stabilizeVideoSupported
),
captureMode = when (it.captureModeStatus) {
CaptureModeProto.CAPTURE_MODE_SINGLE_STREAM -> CaptureMode.SINGLE_STREAM
CaptureModeProto.CAPTURE_MODE_MULTI_STREAM -> CaptureMode.MULTI_STREAM
Expand Down Expand Up @@ -174,4 +179,35 @@ class LocalSettingsRepository @Inject constructor(
.build()
}
}

override suspend fun updateVideoStabilizationSupported(isSupported: Boolean) {
jcaSettings.updateData { currentSettings ->
currentSettings.toBuilder()
.setStabilizeVideoSupported(isSupported)
.build()
}
}

override suspend fun updatePreviewStabilizationSupported(isSupported: Boolean) {
jcaSettings.updateData { currentSettings ->
currentSettings.toBuilder()
.setStabilizeVideoSupported(isSupported)
.build()
}
}

private fun getSupportedStabilization(
previewSupport: Boolean,
videoSupport: Boolean
): SupportedStabilizationMode {
return if (previewSupport && videoSupport) {
SupportedStabilizationMode.FULL
} else if (!previewSupport && videoSupport) {
SupportedStabilizationMode.VIDEO_ONLY
} else if (previewSupport && !videoSupport) {
SupportedStabilizationMode.PREVIEW_ONLY
} else {
SupportedStabilizationMode.UNSUPPORTED
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,9 @@ interface SettingsRepository {
suspend fun updatePreviewStabilization(stabilization: Stabilization)
suspend fun updateVideoStabilization(stabilization: Stabilization)

suspend fun updateVideoStabilizationSupported(isSupported: Boolean)

suspend fun updatePreviewStabilizationSupported(isSupported: Boolean)

suspend fun getCameraAppSettings(): CameraAppSettings
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ data class CameraAppSettings(
val captureMode: CaptureMode = CaptureMode.MULTI_STREAM,
val aspectRatio: AspectRatio = AspectRatio.NINE_SIXTEEN,
val previewStabilization: Stabilization = Stabilization.UNDEFINED,
val videoCaptureStabilization: Stabilization = Stabilization.UNDEFINED
val videoCaptureStabilization: Stabilization = Stabilization.UNDEFINED,
val supportedStabilizationMode: SupportedStabilizationMode =
SupportedStabilizationMode.UNSUPPORTED
)

val DEFAULT_CAMERA_APP_SETTINGS = CameraAppSettings()
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright (C) 2023 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 com.google.jetpackcamera.settings.model

/** Enum class representing the device's supported video stabilization configurations. */
enum class SupportedStabilizationMode {
/** Device supports both Preview and Video stabilization. */
FULL,

/** Device supports only Video stabilization.*/
VIDEO_ONLY,

/** Device supports only Preview stabilization.*/
PREVIEW_ONLY,

/** Device doesn't support any stabilization.*/
UNSUPPORTED
Kimblebee marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,13 @@ package com.google.jetpackcamera.settings.test

import androidx.datastore.core.CorruptionException
import androidx.datastore.core.Serializer
import com.google.jetpackcamera.settings.AspectRatio
import com.google.jetpackcamera.settings.CaptureMode
import com.google.jetpackcamera.settings.DarkMode
import com.google.jetpackcamera.settings.FlashMode
import com.google.jetpackcamera.settings.JcaSettings
import com.google.jetpackcamera.settings.PreviewStabilization
import com.google.jetpackcamera.settings.VideoStabilization
import com.google.protobuf.InvalidProtocolBufferException
import java.io.IOException
import java.io.InputStream
Expand All @@ -31,6 +36,15 @@ class FakeJcaSettingsSerializer(
override val defaultValue: JcaSettings = JcaSettings.newBuilder()
.setDarkModeStatus(DarkMode.DARK_MODE_SYSTEM)
.setDefaultFrontCamera(false)
.setBackCameraAvailable(true)
.setFrontCameraAvailable(true)
.setFlashModeStatus(FlashMode.FLASH_MODE_OFF)
.setAspectRatioStatus(AspectRatio.ASPECT_RATIO_NINE_SIXTEEN)
.setCaptureModeStatus(CaptureMode.CAPTURE_MODE_MULTI_STREAM)
.setStabilizePreview(PreviewStabilization.PREVIEW_STABILIZATION_UNDEFINED)
.setStabilizeVideo(VideoStabilization.VIDEO_STABILIZATION_UNDEFINED)
.setStabilizeVideoSupported(false)
.setStabilizePreviewSupported(false)
.build()

override suspend fun readFrom(input: InputStream): JcaSettings {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,14 @@ import com.google.jetpackcamera.settings.model.DEFAULT_CAMERA_APP_SETTINGS
import com.google.jetpackcamera.settings.model.DarkMode
import com.google.jetpackcamera.settings.model.FlashMode
import com.google.jetpackcamera.settings.model.Stabilization
import com.google.jetpackcamera.settings.model.SupportedStabilizationMode
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow

object FakeSettingsRepository : SettingsRepository {
var currentCameraSettings: CameraAppSettings = DEFAULT_CAMERA_APP_SETTINGS
private var isPreviewStabilizationSupported: Boolean = false
private var isVideoStabilizationSupported: Boolean = false

override val cameraAppSettings: Flow<CameraAppSettings> = flow { emit(currentCameraSettings) }

Expand All @@ -36,8 +39,8 @@ object FakeSettingsRepository : SettingsRepository {
currentCameraSettings = currentCameraSettings.copy(isFrontCameraFacing = newLensFacing)
}

override suspend fun updateDarkModeStatus(darkmode: DarkMode) {
currentCameraSettings = currentCameraSettings.copy(darkMode = darkmode)
override suspend fun updateDarkModeStatus(darkMode: DarkMode) {
currentCameraSettings = currentCameraSettings.copy(darkMode = darkMode)
}

override suspend fun updateFlashModeStatus(flashMode: FlashMode) {
Expand All @@ -64,14 +67,42 @@ object FakeSettingsRepository : SettingsRepository {
}

override suspend fun updatePreviewStabilization(stabilization: Stabilization) {
TODO("Not yet implemented")
currentCameraSettings =
currentCameraSettings.copy(previewStabilization = stabilization)
}

override suspend fun updateVideoStabilization(stabilization: Stabilization) {
TODO("Not yet implemented")
currentCameraSettings =
currentCameraSettings.copy(videoCaptureStabilization = stabilization)
}

override suspend fun updateVideoStabilizationSupported(isSupported: Boolean) {
isVideoStabilizationSupported = isSupported
setSupportedStabilizationMode()
}

override suspend fun updatePreviewStabilizationSupported(isSupported: Boolean) {
isPreviewStabilizationSupported = isSupported
setSupportedStabilizationMode()
}

private fun setSupportedStabilizationMode() {
val stabilizationMode =
if (isPreviewStabilizationSupported && isVideoStabilizationSupported) {
SupportedStabilizationMode.FULL
} else if (isPreviewStabilizationSupported == false &&
isVideoStabilizationSupported == true
) {
SupportedStabilizationMode.VIDEO_ONLY
} else {
SupportedStabilizationMode.UNSUPPORTED
}
currentCameraSettings =
currentCameraSettings.copy(supportedStabilizationMode = stabilizationMode)
}

override suspend fun updateAspectRatio(aspectRatio: AspectRatio) {
TODO("Not yet implemented")
currentCameraSettings =
currentCameraSettings.copy(aspectRatio = aspectRatio)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,6 @@ message JcaSettings {
CaptureMode capture_mode_status = 8;
PreviewStabilization stabilize_preview = 9;
VideoStabilization stabilize_video = 10;
bool stabilize_video_supported = 11;
bool stabilize_preview_supported = 12;
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import com.google.jetpackcamera.settings.model.CameraAppSettings
import com.google.jetpackcamera.settings.model.CaptureMode
import com.google.jetpackcamera.settings.model.FlashMode
import com.google.jetpackcamera.settings.model.Stabilization
import com.google.jetpackcamera.settings.model.SupportedStabilizationMode
import dagger.hilt.android.scopes.ViewModelScoped
import java.io.FileNotFoundException
import java.lang.RuntimeException
Expand Down Expand Up @@ -104,6 +105,7 @@ constructor(
private lateinit var stabilizePreviewMode: Stabilization
private lateinit var stabilizeVideoMode: Stabilization
private lateinit var surfaceProvider: Preview.SurfaceProvider
private lateinit var supportedStabilizationMode: SupportedStabilizationMode
private var isFrontFacing = true

private val screenFlashEvents: MutableSharedFlow<CameraUseCase.ScreenFlashEvent> =
Expand All @@ -114,10 +116,9 @@ constructor(
this.captureMode = currentCameraSettings.captureMode
this.stabilizePreviewMode = currentCameraSettings.previewStabilization
this.stabilizeVideoMode = currentCameraSettings.videoCaptureStabilization
this.supportedStabilizationMode = currentCameraSettings.supportedStabilizationMode
setFlashMode(currentCameraSettings.flashMode, currentCameraSettings.isFrontCameraFacing)
cameraProvider = ProcessCameraProvider.getInstance(application).await()
videoCaptureUseCase = createVideoUseCase()
updateUseCaseGroup()

val availableCameraLens =
listOf(
Expand All @@ -126,15 +127,16 @@ constructor(
).filter { lensFacing ->
cameraProvider.hasCamera(cameraLensToSelector(lensFacing))
}

// updates values for available camera lens if necessary
coroutineScope {
settingsRepository.updateAvailableCameraLens(
availableCameraLens.contains(CameraSelector.LENS_FACING_FRONT),
availableCameraLens.contains(CameraSelector.LENS_FACING_BACK)
)
settingsRepository.updateVideoStabilizationSupported(isStabilizationSupported())
}

videoCaptureUseCase = createVideoUseCase()
updateUseCaseGroup()
return availableCameraLens
}

Expand Down Expand Up @@ -413,22 +415,31 @@ constructor(
useCaseGroup = useCaseGroupBuilder.build()
}

private fun createVideoUseCase(): VideoCapture<Recorder> {
/**
* Checks if video stabilization is supported by the device.
*
*/
private fun isStabilizationSupported(): Boolean {
val availableCameraInfo = cameraProvider.availableCameraInfos
val cameraSelector = if (isFrontFacing) {
CameraSelector.DEFAULT_FRONT_CAMERA
} else {
CameraSelector.DEFAULT_BACK_CAMERA
}
val videoCaptureBuilder = VideoCapture.Builder(recorder)

val isVideoStabilizationSupported =
cameraSelector.filter(availableCameraInfo).firstOrNull()?.let {
Recorder.getVideoCapabilities(it).isStabilizationSupported
} ?: false

return isVideoStabilizationSupported
}

private fun createVideoUseCase(): VideoCapture<Recorder> {
val videoCaptureBuilder = VideoCapture.Builder(recorder)

// set video stabilization
if (isVideoStabilizationSupported && stabilizeVideoMode != Stabilization.UNDEFINED) {

if (shouldVideoBeStabilized()) {
val isStabilized = when (stabilizeVideoMode) {
Stabilization.ON -> true
Stabilization.OFF, Stabilization.UNDEFINED -> false
Expand All @@ -438,20 +449,28 @@ constructor(
return videoCaptureBuilder.build()
}

private fun createPreviewUseCase(): Preview {
val availableCameraInfo = cameraProvider.availableCameraInfos
val cameraSelector = if (isFrontFacing) {
CameraSelector.DEFAULT_FRONT_CAMERA
} else {
CameraSelector.DEFAULT_BACK_CAMERA
}
val isPreviewStabilizationSupported =
Kimblebee marked this conversation as resolved.
Show resolved Hide resolved
cameraSelector.filter(availableCameraInfo).firstOrNull()?.let {
Preview.getPreviewCapabilities(it).isStabilizationSupported
} ?: false
private fun shouldVideoBeStabilized(): Boolean {
// video is supported by the device AND
// video is on OR preview is on
return (supportedStabilizationMode != SupportedStabilizationMode.UNSUPPORTED) &&
(
// high quality (video only) selected
(
stabilizeVideoMode == Stabilization.ON &&
stabilizePreviewMode == Stabilization.UNDEFINED
) ||
// or on is selected
(
stabilizePreviewMode == Stabilization.ON &&
stabilizeVideoMode != Stabilization.OFF
)
)
}

private fun createPreviewUseCase(): Preview {
val previewUseCaseBuilder = Preview.Builder()
if (isPreviewStabilizationSupported && stabilizePreviewMode != Stabilization.UNDEFINED) {
// set preview stabilization
if (shouldPreviewBeStabilized()) {
val isStabilized = when (stabilizePreviewMode) {
Stabilization.ON -> true
else -> false
Expand All @@ -461,6 +480,14 @@ constructor(
return previewUseCaseBuilder.build()
}

private fun shouldPreviewBeStabilized(): Boolean {
return (
supportedStabilizationMode == SupportedStabilizationMode.FULL ||
supportedStabilizationMode == SupportedStabilizationMode.PREVIEW_ONLY
) &&
stabilizePreviewMode == Stabilization.ON
}

// converts LensFacing from datastore to @LensFacing Int value
private fun getLensFacing(isFrontFacing: Boolean): Int = when (isFrontFacing) {
true -> CameraSelector.LENS_FACING_FRONT
Expand Down
Loading
Loading