Merge pull request #335 from KryptKode/feat/camera-x

adjust preview based on selected aspect ratio
This commit is contained in:
Tibor Kaputa 2022-07-16 09:19:16 +02:00 committed by GitHub
commit d2878d294a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 59 additions and 100 deletions

View File

@ -50,8 +50,13 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera
private var mCurrVideoRecTimer = 0
var mLastHandledOrientation = 0
@Suppress("DEPRECATION")
override fun onCreate(savedInstanceState: Bundle?) {
window.addFlags(
WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD or
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or
WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON or
WindowManager.LayoutParams.FLAG_FULLSCREEN
)
useDynamicTheme = false
super.onCreate(savedInstanceState)
appLaunched(BuildConfig.APPLICATION_ID)
@ -62,22 +67,6 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera
supportActionBar?.hide()
checkWhatsNewDialog()
setupOrientationEventListener()
if (isRPlus()) {
setShowWhenLocked(true)
setTurnScreenOn(true)
window.insetsController?.hide(WindowInsets.Type.statusBars())
} else if (isOreoMr1Plus()) {
setShowWhenLocked(true)
setTurnScreenOn(true)
window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
} else {
window.addFlags(
WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD or
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or
WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON or
WindowManager.LayoutParams.FLAG_FULLSCREEN
)
}
}
override fun onResume() {

View File

@ -15,8 +15,6 @@ const val FLASHLIGHT_STATE = "flashlight_state"
const val INIT_PHOTO_MODE = "init_photo_mode"
const val BACK_PHOTO_RESOLUTION_INDEX = "back_photo_resolution_index_2"
const val BACK_VIDEO_RESOLUTION_INDEX = "back_video_resolution_index_2"
const val BACK_VIDEO_QUALITY = "back_video_quality_2"
const val FRONT_VIDEO_QUALITY = "front_video_quality_2"
const val FRONT_PHOTO_RESOLUTION_INDEX = "front_photo_resolution_index_2"
const val FRONT_VIDEO_RESOLUTION_INDEX = "front_video_resolution_index_2"
const val KEEP_SETTINGS_VISIBLE = "keep_settings_visible"

View File

@ -4,9 +4,6 @@ import android.content.Context
import android.graphics.ImageFormat
import android.hardware.camera2.CameraCharacteristics
import android.hardware.camera2.CameraManager
import android.hardware.camera2.params.StreamConfigurationMap
import android.media.MediaRecorder
import android.util.Size
import androidx.appcompat.app.AppCompatActivity
import androidx.camera.core.CameraSelector
import com.simplemobiletools.camera.extensions.config
@ -19,8 +16,6 @@ class ImageQualityManager(
) {
companion object {
private const val MAX_VIDEO_WIDTH = 4096
private const val MAX_VIDEO_HEIGHT = 2160
private val CAMERA_LENS = arrayOf(CameraCharacteristics.LENS_FACING_FRONT, CameraCharacteristics.LENS_FACING_BACK)
}
@ -29,29 +24,25 @@ class ImageQualityManager(
private val imageQualities = mutableListOf<CameraSelectorImageQualities>()
fun initSupportedQualities() {
for (cameraId in cameraManager.cameraIdList) {
try {
val characteristics = cameraManager.getCameraCharacteristics(cameraId)
for (lens in CAMERA_LENS) {
if (characteristics.get(CameraCharacteristics.LENS_FACING) == lens) {
val configMap = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) ?: continue
val imageSizes = configMap.getOutputSizes(ImageFormat.JPEG).map { MySize(it.width, it.height) }
val cameraSelector = lens.toCameraSelector()
imageQualities.add(CameraSelectorImageQualities(cameraSelector, imageSizes))
if (imageQualities.isEmpty()) {
for (cameraId in cameraManager.cameraIdList) {
try {
val characteristics = cameraManager.getCameraCharacteristics(cameraId)
for (lens in CAMERA_LENS) {
if (characteristics.get(CameraCharacteristics.LENS_FACING) == lens) {
val configMap = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) ?: continue
val imageSizes = configMap.getOutputSizes(ImageFormat.JPEG).map { MySize(it.width, it.height) }
val cameraSelector = lens.toCameraSelector()
imageQualities.add(CameraSelectorImageQualities(cameraSelector, imageSizes))
}
}
} catch (e: Exception) {
activity.showErrorToast(e)
}
} catch (e: Exception) {
activity.showErrorToast(e)
}
}
}
private fun getAvailableVideoSizes(configMap: StreamConfigurationMap): List<Size> {
return configMap.getOutputSizes(MediaRecorder::class.java).filter {
it.width <= MAX_VIDEO_WIDTH && it.height <= MAX_VIDEO_HEIGHT
}
}
private fun Int.toCameraSelector(): CameraSelector {
return if (this == CameraCharacteristics.LENS_FACING_FRONT) {
CameraSelector.DEFAULT_FRONT_CAMERA
@ -60,20 +51,19 @@ class ImageQualityManager(
}
}
fun getUserSelectedResolution(cameraSelector: CameraSelector): Size? {
fun getUserSelectedResolution(cameraSelector: CameraSelector): MySize {
val index = if (cameraSelector == CameraSelector.DEFAULT_FRONT_CAMERA) config.frontPhotoResIndex else config.backPhotoResIndex
return imageQualities.filter { it.camSelector == cameraSelector }
.flatMap { it.qualities }
.sortedByDescending { it.pixels }
.sortedWith(compareByDescending<MySize> { it.ratio }.thenByDescending { it.pixels })
.distinctBy { it.pixels }
.map { Size(it.width, it.height) }
.getOrNull(index)
.filter { it.megaPixels != "0.0" }[index]
}
fun getSupportedResolutions(cameraSelector: CameraSelector): List<MySize> {
return imageQualities.filter { it.camSelector == cameraSelector }
.flatMap { it.qualities }
.sortedByDescending { it.pixels }
.sortedWith(compareByDescending<MySize> { it.ratio }.thenByDescending { it.pixels })
.distinctBy { it.pixels }
.filter { it.megaPixels != "0.0" }
}

View File

@ -6,7 +6,6 @@ import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.video.Quality
import androidx.camera.video.QualitySelector
import com.simplemobiletools.camera.extensions.config
import com.simplemobiletools.camera.extensions.toCameraXQuality
import com.simplemobiletools.camera.extensions.toVideoQuality
import com.simplemobiletools.camera.models.CameraSelectorVideoQualities
import com.simplemobiletools.camera.models.VideoQuality
@ -45,10 +44,10 @@ class VideoQualityManager(
}
}
fun getUserSelectedQuality(cameraSelector: CameraSelector): Quality {
fun getUserSelectedQuality(cameraSelector: CameraSelector): VideoQuality {
var selectionIndex = if (cameraSelector == CameraSelector.DEFAULT_FRONT_CAMERA) config.frontVideoResIndex else config.backVideoResIndex
selectionIndex = selectionIndex.coerceAtLeast(0)
return getSupportedQualities(cameraSelector).getOrElse(selectionIndex) { VideoQuality.HD }.toCameraXQuality()
return getSupportedQualities(cameraSelector).getOrElse(selectionIndex) { VideoQuality.HD }
}
fun getSupportedQualities(cameraSelector: CameraSelector): List<VideoQuality> {

View File

@ -18,7 +18,6 @@ import androidx.core.content.ContextCompat
import androidx.core.view.doOnLayout
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.window.layout.WindowMetricsCalculator
import com.bumptech.glide.load.ImageHeaderParser.UNKNOWN_ORIENTATION
import com.simplemobiletools.camera.R
import com.simplemobiletools.camera.dialogs.ChangeResolutionDialogX
@ -26,11 +25,9 @@ import com.simplemobiletools.camera.extensions.*
import com.simplemobiletools.camera.helpers.*
import com.simplemobiletools.camera.interfaces.MyPreview
import com.simplemobiletools.camera.models.MediaOutput
import com.simplemobiletools.camera.models.MySize
import com.simplemobiletools.commons.extensions.toast
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
class CameraXPreview(
private val activity: AppCompatActivity,
@ -42,9 +39,6 @@ class CameraXPreview(
) : MyPreview, DefaultLifecycleObserver {
companion object {
private const val RATIO_4_3_VALUE = 4.0 / 3.0
private const val RATIO_16_9_VALUE = 16.0 / 9.0
// Auto focus is 1/6 of the area.
private const val AF_SIZE = 1.0f / 6.0f
private const val AE_SIZE = AF_SIZE * 1.5f
@ -55,7 +49,6 @@ class CameraXPreview(
private val mainExecutor = ContextCompat.getMainExecutor(activity)
private val displayManager = activity.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
private val mediaSoundHelper = MediaSoundHelper()
private val windowMetricsCalculator = WindowMetricsCalculator.getOrCreate()
private val videoQualityManager = VideoQualityManager(activity)
private val imageQualityManager = ImageQualityManager(activity)
private val exifRemover = ExifRemover(contentResolver)
@ -73,9 +66,13 @@ class CameraXPreview(
in 225 until 315 -> Surface.ROTATION_90
else -> Surface.ROTATION_0
}
preview?.targetRotation = rotation
imageCapture?.targetRotation = rotation
videoCapture?.targetRotation = rotation
if (lastRotation != rotation) {
preview?.targetRotation = rotation
imageCapture?.targetRotation = rotation
videoCapture?.targetRotation = rotation
lastRotation = rotation
}
}
}
@ -89,6 +86,7 @@ class CameraXPreview(
private var cameraSelector = config.lastUsedCameraLens.toCameraSelector()
private var flashMode = FLASH_MODE_OFF
private var isPhotoCapture = initInPhotoMode
private var lastRotation = 0
init {
bindToLifeCycle()
@ -122,17 +120,18 @@ class CameraXPreview(
private fun bindCameraUseCases() {
val cameraProvider = cameraProvider ?: throw IllegalStateException("Camera initialization failed.")
val metrics = windowMetricsCalculator.computeCurrentWindowMetrics(activity).bounds
val aspectRatio = if (isPhotoCapture) {
aspectRatio(metrics.width(), metrics.height())
val rotation = previewView.display.rotation
val resolution = if (isPhotoCapture) {
imageQualityManager.getUserSelectedResolution(cameraSelector)
} else {
val selectedQuality = videoQualityManager.getUserSelectedQuality(cameraSelector)
selectedQuality.getAspectRatio()
MySize(selectedQuality.width, selectedQuality.height)
}
val rotation = previewView.display.rotation
preview = buildPreview(aspectRatio, rotation)
val captureUseCase = getCaptureUseCase(aspectRatio, rotation)
val rotatedResolution = getRotatedResolution(resolution, rotation)
preview = buildPreview(rotatedResolution, rotation)
val captureUseCase = getCaptureUseCase(rotatedResolution, rotation)
cameraProvider.unbindAll()
camera = cameraProvider.bindToLifecycle(
activity,
@ -167,10 +166,10 @@ class CameraXPreview(
}
}
private fun getCaptureUseCase(aspectRatio: Int, rotation: Int): UseCase {
private fun getCaptureUseCase(resolution: Size, rotation: Int): UseCase {
return if (isPhotoCapture) {
cameraProvider?.unbind(videoCapture)
buildImageCapture(aspectRatio, rotation).also {
buildImageCapture(resolution, rotation).also {
imageCapture = it
}
} else {
@ -181,22 +180,17 @@ class CameraXPreview(
}
}
private fun buildImageCapture(aspectRatio: Int, rotation: Int): ImageCapture {
private fun buildImageCapture(resolution: Size, rotation: Int): ImageCapture {
return Builder()
.setCaptureMode(CAPTURE_MODE_MAXIMIZE_QUALITY)
.setFlashMode(flashMode)
.setJpegQuality(config.photoQuality)
.setTargetRotation(rotation)
.apply {
imageQualityManager.getUserSelectedResolution(cameraSelector)?.let { resolution ->
val rotatedResolution = getRotatedResolution(rotation, resolution)
setTargetResolution(rotatedResolution)
} ?: setTargetAspectRatio(aspectRatio)
}
.setTargetResolution(resolution)
.build()
}
private fun getRotatedResolution(rotationDegrees: Int, resolution: Size): Size {
private fun getRotatedResolution(resolution: MySize, rotationDegrees: Int): Size {
return if (rotationDegrees == Surface.ROTATION_0 || rotationDegrees == Surface.ROTATION_180) {
Size(resolution.height, resolution.width)
} else {
@ -204,16 +198,16 @@ class CameraXPreview(
}
}
private fun buildPreview(aspectRatio: Int, rotation: Int): Preview {
private fun buildPreview(resolution: Size, rotation: Int): Preview {
return Preview.Builder()
.setTargetRotation(rotation)
.setTargetAspectRatio(aspectRatio)
.setTargetResolution(resolution)
.build()
}
private fun buildVideoCapture(): VideoCapture<Recorder> {
val qualitySelector = QualitySelector.from(
videoQualityManager.getUserSelectedQuality(cameraSelector),
videoQualityManager.getUserSelectedQuality(cameraSelector).toCameraXQuality(),
FallbackStrategy.higherQualityOrLowerThan(Quality.SD),
)
val recorder = Recorder.Builder()
@ -222,14 +216,6 @@ class CameraXPreview(
return VideoCapture.withOutput(recorder)
}
private fun aspectRatio(width: Int, height: Int): Int {
val previewRatio = max(width, height).toDouble() / min(width, height)
if (abs(previewRatio - RATIO_4_3_VALUE) <= abs(previewRatio - RATIO_16_9_VALUE)) {
return AspectRatio.RATIO_4_3
}
return AspectRatio.RATIO_16_9
}
private fun hasBackCamera(): Boolean {
return cameraProvider?.hasCamera(CameraSelector.DEFAULT_BACK_CAMERA) ?: false
}
@ -288,7 +274,7 @@ class CameraXPreview(
activity,
isFrontCameraInUse(),
imageQualityManager.getSupportedResolutions(cameraSelector),
videoQualityManager.getSupportedQualities(cameraSelector)
videoQualityManager.getSupportedQualities(cameraSelector),
) {
if (oldQuality != videoQualityManager.getUserSelectedQuality(cameraSelector)) {
currentRecording?.stop()

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/view_holder"
android:layout_width="match_parent"
@ -9,7 +10,8 @@
<androidx.camera.view.PreviewView
android:id="@+id/preview_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
android:layout_height="match_parent"
app:scaleType="fitStart" />
<View
android:id="@+id/capture_black_screen"

View File

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/change_resolution_holder"
android:layout_width="match_parent"
android:layout_height="match_parent"
@ -20,17 +19,15 @@
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toStartOf="@+id/change_resolution_photo"
android:layout_toLeftOf="@+id/change_resolution_photo"
android:text="@string/photo"/>
android:text="@string/photo" />
<com.simplemobiletools.commons.views.MyTextView
android:id="@+id/change_resolution_photo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:background="@null"
android:clickable="false"/>
android:clickable="false" />
</RelativeLayout>
@ -48,17 +45,15 @@
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toStartOf="@+id/change_resolution_video"
android:layout_toLeftOf="@+id/change_resolution_video"
android:text="@string/video"/>
android:text="@string/video" />
<com.simplemobiletools.commons.views.MyTextView
android:id="@+id/change_resolution_video"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:background="@null"
android:clickable="false"/>
android:clickable="false" />
</RelativeLayout>
</LinearLayout>