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

ResolutionSelector for selecting video size #276

Open
wants to merge 1 commit 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 @@ -17,22 +17,26 @@ package com.google.jetpackcamera.core.camera

import android.annotation.SuppressLint
import android.hardware.camera2.CameraCharacteristics
import android.util.Size
import androidx.annotation.OptIn
import androidx.camera.camera2.interop.Camera2CameraInfo
import androidx.camera.camera2.interop.ExperimentalCamera2Interop
import androidx.camera.core.CameraInfo
import androidx.camera.core.CameraSelector
import androidx.camera.core.DynamicRange as CXDynamicRange
import androidx.camera.core.ExperimentalImageCaptureOutputFormat
import androidx.camera.core.ImageCapture
import androidx.camera.core.Preview
import androidx.camera.core.UseCase
import androidx.camera.core.UseCaseGroup
import androidx.camera.video.Quality
import androidx.camera.video.Recorder
import androidx.camera.video.VideoCapture
import com.google.jetpackcamera.settings.model.DynamicRange
import com.google.jetpackcamera.settings.model.ImageOutputFormat
import com.google.jetpackcamera.settings.model.LensFacing
import java.util.Arrays
import java.util.Collections
import androidx.camera.core.DynamicRange as CXDynamicRange

val CameraInfo.appLensFacing: LensFacing
get() = when (this.lensFacing) {
Expand Down Expand Up @@ -60,6 +64,16 @@ fun DynamicRange.toCXDynamicRange(): CXDynamicRange {
}
}

fun Quality.toSupportedSizes(): List<Size> {
return when (this) {
Quality.SD -> listOf(Size(720, 480), Size(640, 480))
Quality.HD -> listOf(Size(1280, 720))
Quality.FHD -> listOf(Size(1920, 1080))
Quality.UHD -> listOf(Size(3840, 2160))
else -> listOf()
}
}

fun LensFacing.toCameraSelector(): CameraSelector = when (this) {
LensFacing.FRONT -> CameraSelector.DEFAULT_FRONT_CAMERA
LensFacing.BACK -> CameraSelector.DEFAULT_BACK_CAMERA
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package com.google.jetpackcamera.core.camera

import android.Manifest
import android.annotation.SuppressLint
import android.content.ContentValues
import android.content.Context
import android.content.pm.PackageManager
Expand All @@ -29,6 +30,7 @@ import android.os.SystemClock
import android.provider.MediaStore
import android.util.Log
import android.util.Range
import android.util.Size
import androidx.annotation.OptIn
import androidx.camera.camera2.interop.Camera2CameraInfo
import androidx.camera.camera2.interop.Camera2Interop
Expand All @@ -48,6 +50,7 @@ import androidx.camera.core.UseCaseGroup
import androidx.camera.core.ViewPort
import androidx.camera.core.resolutionselector.AspectRatioStrategy
import androidx.camera.core.resolutionselector.ResolutionSelector
import androidx.camera.core.resolutionselector.ResolutionStrategy
import androidx.camera.video.FileDescriptorOutputOptions
import androidx.camera.video.FileOutputOptions
import androidx.camera.video.MediaStoreOutputOptions
Expand All @@ -71,11 +74,6 @@ import com.google.jetpackcamera.settings.model.FlashMode
import com.google.jetpackcamera.settings.model.ImageOutputFormat
import com.google.jetpackcamera.settings.model.LensFacing
import com.google.jetpackcamera.settings.model.Stabilization
import java.io.File
import java.util.Date
import java.util.concurrent.Executor
import kotlin.coroutines.ContinuationInterceptor
import kotlin.math.abs
import kotlinx.atomicfu.atomic
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineStart
Expand All @@ -91,6 +89,11 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import java.io.File
import java.util.Date
import java.util.concurrent.Executor
import kotlin.coroutines.ContinuationInterceptor
import kotlin.math.abs

private const val TAG = "CameraSession"

Expand All @@ -116,6 +119,7 @@ internal suspend fun runSingleCameraSession(
aspectRatio = sessionSettings.aspectRatio,
targetFrameRate = sessionSettings.targetFrameRate,
dynamicRange = sessionSettings.dynamicRange,
videoSize = sessionSettings.videoSize,
imageFormat = sessionSettings.imageFormat,
useCaseMode = useCaseMode,
effect = when (sessionSettings.captureMode) {
Expand Down Expand Up @@ -244,6 +248,7 @@ internal fun createUseCaseGroup(
aspectRatio: AspectRatio,
targetFrameRate: Int,
dynamicRange: DynamicRange,
videoSize: Size? = null,
imageFormat: ImageOutputFormat,
useCaseMode: CameraUseCase.UseCaseMode,
effect: CameraEffect? = null
Expand All @@ -266,6 +271,7 @@ internal fun createUseCaseGroup(
targetFrameRate,
stabilizeVideoMode,
dynamicRange,
videoSize,
backgroundDispatcher
)
} else {
Expand Down Expand Up @@ -330,12 +336,14 @@ private fun createImageUseCase(
return builder.build()
}

@SuppressLint("RestrictedApi")
private fun createVideoUseCase(
cameraInfo: CameraInfo,
aspectRatio: AspectRatio,
targetFrameRate: Int,
stabilizeVideoMode: Stabilization,
dynamicRange: DynamicRange,
videoSize: Size?,
backgroundDispatcher: CoroutineDispatcher
): VideoCapture<Recorder> {
val sensorLandscapeRatio = cameraInfo.sensorLandscapeRatio
Expand All @@ -355,6 +363,12 @@ private fun createVideoUseCase(
}

setDynamicRange(dynamicRange.toCXDynamicRange())

if (videoSize != null) {
setResolutionSelector(
getResolutionSelector(cameraInfo.sensorLandscapeRatio, aspectRatio)
)
}
}.build()
}

Expand Down Expand Up @@ -401,7 +415,8 @@ private fun createPreviewUseCase(

private fun getResolutionSelector(
sensorLandscapeRatio: Float,
aspectRatio: AspectRatio
aspectRatio: AspectRatio,
videoSize: Size? = null
): ResolutionSelector {
val aspectRatioStrategy = when (aspectRatio) {
AspectRatio.THREE_FOUR -> AspectRatioStrategy.RATIO_4_3_FALLBACK_AUTO_STRATEGY
Expand All @@ -419,7 +434,13 @@ private fun getResolutionSelector(
}
}
}
return ResolutionSelector.Builder().setAspectRatioStrategy(aspectRatioStrategy).build()
val builder = ResolutionSelector.Builder().setAspectRatioStrategy(aspectRatioStrategy)
if (videoSize != null) {
builder.setResolutionStrategy(
ResolutionStrategy(videoSize, ResolutionStrategy.FALLBACK_RULE_CLOSEST_HIGHER_THEN_LOWER)
)
}
return builder.build()
}

context(CameraSessionContext)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package com.google.jetpackcamera.core.camera

import android.util.Size
import androidx.camera.core.CameraInfo
import com.google.jetpackcamera.settings.model.AspectRatio
import com.google.jetpackcamera.settings.model.CaptureMode
Expand All @@ -41,6 +42,7 @@ internal sealed interface PerpetualSessionSettings {
val stabilizePreviewMode: Stabilization,
val stabilizeVideoMode: Stabilization,
val dynamicRange: DynamicRange,
val videoSize: Size?,
val imageFormat: ImageOutputFormat
) : PerpetualSessionSettings

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 android.content.ContentResolver
import android.net.Uri
import android.util.Size
import androidx.camera.core.ImageCapture
import androidx.camera.core.SurfaceRequest
import com.google.jetpackcamera.settings.model.AspectRatio
Expand Down Expand Up @@ -96,6 +97,8 @@ interface CameraUseCase {

suspend fun setAspectRatio(aspectRatio: AspectRatio)

suspend fun setVideoSize(size: Size)

suspend fun setLensFacing(lensFacing: LensFacing)

suspend fun tapToFocus(x: Float, y: Float)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import android.os.Build
import android.os.Environment
import android.provider.MediaStore
import android.util.Log
import android.util.Size
import androidx.camera.core.CameraInfo
import androidx.camera.core.CameraSelector
import androidx.camera.core.DynamicRange as CXDynamicRange
Expand Down Expand Up @@ -146,10 +147,21 @@ constructor(
for (lensFacing in availableCameraLenses) {
val selector = lensFacing.toCameraSelector()
selector.filter(availableCameraInfos).firstOrNull()?.let { camInfo ->
val videoCapabilities = Recorder.getVideoCapabilities(camInfo)
val supportedDynamicRanges =
Recorder.getVideoCapabilities(camInfo).supportedDynamicRanges
videoCapabilities.supportedDynamicRanges
.mapNotNull(CXDynamicRange::toSupportedAppDynamicRange)
.toSet()
val supportedVideoSizesMap = mutableMapOf<DynamicRange, List<Size>>()
for (dynamicRange in supportedDynamicRanges) {
val supportedSizes = mutableListOf<Size>()
for (quality in videoCapabilities.getSupportedQualities(
dynamicRange.toCXDynamicRange()
)) {
supportedSizes.addAll(quality.toSupportedSizes())
}
supportedVideoSizesMap[dynamicRange] = supportedSizes
}

val supportedStabilizationModes = buildSet {
if (camInfo.isPreviewStabilizationSupported) {
Expand Down Expand Up @@ -179,6 +191,7 @@ constructor(
Pair(CaptureMode.SINGLE_STREAM, setOf(ImageOutputFormat.JPEG)),
Pair(CaptureMode.MULTI_STREAM, supportedImageFormats)
),
supportedVideoSizesMap = supportedVideoSizesMap,
hasFlashUnit = hasFlashUnit
)
)
Expand All @@ -197,6 +210,7 @@ constructor(
.tryApplyFrameRateConstraints()
.tryApplyStabilizationConstraints()
.tryApplyConcurrentCameraModeConstraints()
.tryApplyVideoSizeConstraints()
if (isDebugMode && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
withContext(iODispatcher) {
val cameraProperties =
Expand Down Expand Up @@ -242,6 +256,7 @@ constructor(
stabilizePreviewMode = currentCameraSettings.previewStabilization,
stabilizeVideoMode = currentCameraSettings.videoCaptureStabilization,
dynamicRange = currentCameraSettings.dynamicRange,
videoSize = currentCameraSettings.videoSize,
imageFormat = currentCameraSettings.imageFormat
)
}
Expand Down Expand Up @@ -556,6 +571,28 @@ constructor(
}
}

private fun CameraAppSettings.tryApplyVideoSizeConstraints(): CameraAppSettings {
return systemConstraints.perLensConstraints[cameraLensFacing]?.let { constraints ->
with(constraints.supportedVideoSizesMap) {
val newVideoSize = if (contains(dynamicRange) &&
!get(dynamicRange).isNullOrEmpty()
) {
if (get(dynamicRange)!!.contains(videoSize)) {
videoSize
} else {
get(dynamicRange)!![0]
}
} else {
null
}

[email protected](
videoSize = newVideoSize
)
}
} ?: this
}

override suspend fun tapToFocus(x: Float, y: Float) {
focusMeteringEvents.send(CameraEvent.FocusMeteringEvent(x, y))
}
Expand All @@ -579,6 +616,14 @@ constructor(
}
}

override suspend fun setVideoSize(size: Size) {
currentSettings.update { old ->
old?.copy(videoSize = size)
?.tryApplyVideoSizeConstraints()
}
// TODO: Update UseCases
}

override suspend fun setCaptureMode(captureMode: CaptureMode) {
currentSettings.update { old ->
old?.copy(captureMode = captureMode)
Expand All @@ -591,6 +636,7 @@ constructor(
currentSettings.update { old ->
old?.copy(dynamicRange = dynamicRange)
?.tryApplyConcurrentCameraModeConstraints()
?.tryApplyVideoSizeConstraints()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
* limitations under the License.
*/
package com.google.jetpackcamera.settings.model

import android.util.Size

const val TARGET_FPS_AUTO = 0
const val UNLIMITED_VIDEO_DURATION = 0L

Expand All @@ -29,6 +32,7 @@ data class CameraAppSettings(
val previewStabilization: Stabilization = Stabilization.UNDEFINED,
val videoCaptureStabilization: Stabilization = Stabilization.UNDEFINED,
val dynamicRange: DynamicRange = DynamicRange.SDR,
val videoSize: Size? = null,
val defaultHdrDynamicRange: DynamicRange = DynamicRange.HLG10,
val defaultHdrImageOutputFormat: ImageOutputFormat = ImageOutputFormat.JPEG_ULTRA_HDR,
val lowLightBoost: LowLightBoost = LowLightBoost.DISABLED,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
*/
package com.google.jetpackcamera.settings.model

import android.util.Size

data class SystemConstraints(
val availableLenses: List<LensFacing>,
val concurrentCamerasSupported: Boolean,
Expand All @@ -26,6 +28,7 @@ data class CameraConstraints(
val supportedFixedFrameRates: Set<Int>,
val supportedDynamicRanges: Set<DynamicRange>,
val supportedImageFormatsMap: Map<CaptureMode, Set<ImageOutputFormat>>,
val supportedVideoSizesMap: Map<DynamicRange, List<Size>>,
val hasFlashUnit: Boolean
)

Expand All @@ -48,6 +51,7 @@ val TYPICAL_SYSTEM_CONSTRAINTS =
Pair(CaptureMode.SINGLE_STREAM, setOf(ImageOutputFormat.JPEG)),
Pair(CaptureMode.MULTI_STREAM, setOf(ImageOutputFormat.JPEG))
),
supportedVideoSizesMap = emptyMap(),
hasFlashUnit = lensFacing == LensFacing.BACK
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,16 @@ package com.google.jetpackcamera.settings
import com.google.jetpackcamera.settings.DisabledRationale.DeviceUnsupportedRationale
import com.google.jetpackcamera.settings.DisabledRationale.LensUnsupportedRationale
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.DEFAULT_CAMERA_APP_SETTINGS
import com.google.jetpackcamera.settings.model.DarkMode
import com.google.jetpackcamera.settings.model.DynamicRange
import com.google.jetpackcamera.settings.model.FlashMode
import com.google.jetpackcamera.settings.model.LensFacing
import com.google.jetpackcamera.settings.model.Stabilization
import com.google.jetpackcamera.settings.model.SystemConstraints
import com.google.jetpackcamera.settings.model.TYPICAL_SYSTEM_CONSTRAINTS
import com.google.jetpackcamera.settings.ui.DEVICE_UNSUPPORTED_TAG
import com.google.jetpackcamera.settings.ui.FPS_UNSUPPORTED_TAG
import com.google.jetpackcamera.settings.ui.LENS_UNSUPPORTED_TAG
Expand Down Expand Up @@ -54,7 +58,9 @@ sealed interface SettingsUiState {
val fpsUiState: FpsUiState,
val lensFlipUiState: FlipLensUiState,
val stabilizationUiState: StabilizationUiState,
val maxVideoDurationUiState: MaxVideoDurationUiState.Enabled
val maxVideoDurationUiState: MaxVideoDurationUiState.Enabled,
val cameraAppSettings: CameraAppSettings,
val systemConstraints: SystemConstraints
) : SettingsUiState
}

Expand Down Expand Up @@ -230,5 +236,7 @@ val TYPICAL_SETTINGS_UISTATE = SettingsUiState.Enabled(
stabilizationUiState =
StabilizationUiState.Disabled(
DeviceUnsupportedRationale(R.string.stabilization_rationale_prefix)
)
),
cameraAppSettings = DEFAULT_CAMERA_APP_SETTINGS,
systemConstraints = TYPICAL_SYSTEM_CONSTRAINTS
)
Loading
Loading