Merge pull request #326 from KryptKode/feat/camera-x
try to handle changing video quality and image resolution
This commit is contained in:
commit
3c16ba8853
|
@ -0,0 +1,104 @@
|
||||||
|
package com.simplemobiletools.camera.dialogs
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import com.simplemobiletools.camera.R
|
||||||
|
import com.simplemobiletools.camera.extensions.config
|
||||||
|
import com.simplemobiletools.camera.models.MySize
|
||||||
|
import com.simplemobiletools.camera.models.VideoQuality
|
||||||
|
import com.simplemobiletools.commons.dialogs.RadioGroupDialog
|
||||||
|
import com.simplemobiletools.commons.extensions.setupDialogStuff
|
||||||
|
import com.simplemobiletools.commons.models.RadioItem
|
||||||
|
import kotlinx.android.synthetic.main.dialog_change_resolution.view.change_resolution_photo
|
||||||
|
import kotlinx.android.synthetic.main.dialog_change_resolution.view.change_resolution_photo_holder
|
||||||
|
import kotlinx.android.synthetic.main.dialog_change_resolution.view.change_resolution_video
|
||||||
|
import kotlinx.android.synthetic.main.dialog_change_resolution.view.change_resolution_video_holder
|
||||||
|
|
||||||
|
class ChangeResolutionDialogX(
|
||||||
|
private val activity: Activity,
|
||||||
|
private val isFrontCamera: Boolean,
|
||||||
|
private val photoResolutions: List<MySize> = listOf(),
|
||||||
|
private val videoResolutions: List<VideoQuality>,
|
||||||
|
private val callback: () -> Unit
|
||||||
|
) {
|
||||||
|
private var dialog: AlertDialog
|
||||||
|
private val config = activity.config
|
||||||
|
|
||||||
|
private val TAG = "ChangeResolutionDialogX"
|
||||||
|
init {
|
||||||
|
val view = LayoutInflater.from(activity).inflate(R.layout.dialog_change_resolution, null).apply {
|
||||||
|
setupPhotoResolutionPicker(this)
|
||||||
|
setupVideoResolutionPicker(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog = AlertDialog.Builder(activity)
|
||||||
|
.setPositiveButton(R.string.ok, null)
|
||||||
|
.create().apply {
|
||||||
|
activity.setupDialogStuff(view, this, if (isFrontCamera) R.string.front_camera else R.string.back_camera)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupPhotoResolutionPicker(view: View) {
|
||||||
|
val items = getFormattedResolutions(photoResolutions)
|
||||||
|
var selectionIndex = if (isFrontCamera) config.frontPhotoResIndex else config.backPhotoResIndex
|
||||||
|
selectionIndex = Math.max(selectionIndex, 0)
|
||||||
|
|
||||||
|
view.change_resolution_photo_holder.setOnClickListener {
|
||||||
|
RadioGroupDialog(activity, items, selectionIndex) {
|
||||||
|
selectionIndex = it as Int
|
||||||
|
Log.w(TAG, "setupPhotoResolutionPicker: selectionIndex=$it")
|
||||||
|
view.change_resolution_photo.text = items[selectionIndex].title
|
||||||
|
if (isFrontCamera) {
|
||||||
|
config.frontPhotoResIndex = it
|
||||||
|
} else {
|
||||||
|
config.backPhotoResIndex = it
|
||||||
|
}
|
||||||
|
dialog.dismiss()
|
||||||
|
callback.invoke()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
view.change_resolution_photo.text = items.getOrNull(selectionIndex)?.title
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupVideoResolutionPicker(view: View) {
|
||||||
|
val items = videoResolutions.mapIndexed { index, videoQuality ->
|
||||||
|
val megapixels = videoQuality.megaPixels
|
||||||
|
val aspectRatio = videoQuality.getAspectRatio(activity)
|
||||||
|
RadioItem(index, "${videoQuality.width} x ${videoQuality.height} ($megapixels MP, $aspectRatio)")
|
||||||
|
}
|
||||||
|
|
||||||
|
val videoQuality = if (isFrontCamera) config.frontVideoQuality else config.backVideoQuality
|
||||||
|
var selectionIndex = videoResolutions.indexOf(videoQuality)
|
||||||
|
|
||||||
|
view.change_resolution_video_holder.setOnClickListener {
|
||||||
|
RadioGroupDialog(activity, ArrayList(items), selectionIndex) {
|
||||||
|
selectionIndex = it as Int
|
||||||
|
val selectedItem = items[selectionIndex]
|
||||||
|
val selectedQuality = videoResolutions[selectionIndex]
|
||||||
|
view.change_resolution_video.text = selectedItem.title
|
||||||
|
if (isFrontCamera) {
|
||||||
|
config.frontVideoQuality = selectedQuality
|
||||||
|
} else {
|
||||||
|
config.backVideoQuality = selectedQuality
|
||||||
|
}
|
||||||
|
dialog.dismiss()
|
||||||
|
callback.invoke()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
view.change_resolution_video.text = items.getOrNull(selectionIndex)?.title
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getFormattedResolutions(resolutions: List<MySize>): ArrayList<RadioItem> {
|
||||||
|
val items = ArrayList<RadioItem>(resolutions.size)
|
||||||
|
val sorted = resolutions.sortedByDescending { it.width * it.height }
|
||||||
|
sorted.forEachIndexed { index, size ->
|
||||||
|
val megapixels = String.format("%.1f", (size.width * size.height.toFloat()) / 1000000)
|
||||||
|
val aspectRatio = size.getAspectRatio(activity)
|
||||||
|
items.add(RadioItem(index, "${size.width} x ${size.height} ($megapixels MP, $aspectRatio)"))
|
||||||
|
}
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
package com.simplemobiletools.camera.extensions
|
||||||
|
|
||||||
|
import androidx.camera.core.AspectRatio
|
||||||
|
import androidx.camera.video.Quality
|
||||||
|
import com.simplemobiletools.camera.models.VideoQuality
|
||||||
|
|
||||||
|
fun Quality.toVideoQuality(): VideoQuality {
|
||||||
|
return when (this) {
|
||||||
|
Quality.UHD -> VideoQuality.UHD
|
||||||
|
Quality.FHD -> VideoQuality.FHD
|
||||||
|
Quality.HD -> VideoQuality.HD
|
||||||
|
Quality.SD -> VideoQuality.SD
|
||||||
|
else -> throw IllegalArgumentException("Unsupported quality: $this")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun VideoQuality.toCameraXQuality(): Quality {
|
||||||
|
return when (this) {
|
||||||
|
VideoQuality.UHD -> Quality.UHD
|
||||||
|
VideoQuality.FHD -> Quality.FHD
|
||||||
|
VideoQuality.HD -> Quality.HD
|
||||||
|
VideoQuality.SD -> Quality.SD
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Quality.getAspectRatio(): Int {
|
||||||
|
return when(this) {
|
||||||
|
Quality.UHD, Quality.FHD, Quality.HD -> AspectRatio.RATIO_16_9
|
||||||
|
Quality.SD -> AspectRatio.RATIO_4_3
|
||||||
|
else -> throw IllegalArgumentException("Unsupported quality: $this")
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ package com.simplemobiletools.camera.helpers
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import androidx.camera.core.CameraSelector
|
import androidx.camera.core.CameraSelector
|
||||||
|
import com.simplemobiletools.camera.models.VideoQuality
|
||||||
import com.simplemobiletools.commons.helpers.BaseConfig
|
import com.simplemobiletools.commons.helpers.BaseConfig
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
|
@ -62,6 +63,20 @@ class Config(context: Context) : BaseConfig(context) {
|
||||||
get() = prefs.getInt(FRONT_PHOTO_RESOLUTION_INDEX, 0)
|
get() = prefs.getInt(FRONT_PHOTO_RESOLUTION_INDEX, 0)
|
||||||
set(frontPhotoResIndex) = prefs.edit().putInt(FRONT_PHOTO_RESOLUTION_INDEX, frontPhotoResIndex).apply()
|
set(frontPhotoResIndex) = prefs.edit().putInt(FRONT_PHOTO_RESOLUTION_INDEX, frontPhotoResIndex).apply()
|
||||||
|
|
||||||
|
var backVideoQuality: VideoQuality
|
||||||
|
get() {
|
||||||
|
val backQuality = prefs.getString(BACK_VIDEO_QUALITY, VideoQuality.UHD.name)
|
||||||
|
return VideoQuality.values().first { it.name == backQuality }
|
||||||
|
}
|
||||||
|
set(backVideoQuality) = prefs.edit().putString(BACK_VIDEO_QUALITY, backVideoQuality.name).apply()
|
||||||
|
|
||||||
|
var frontVideoQuality: VideoQuality
|
||||||
|
get() {
|
||||||
|
val frontQuality = prefs.getString(FRONT_VIDEO_QUALITY, VideoQuality.UHD.name)
|
||||||
|
return VideoQuality.values().first { it.name == frontQuality }
|
||||||
|
}
|
||||||
|
set(frontVideoQuality) = prefs.edit().putString(FRONT_VIDEO_QUALITY, frontVideoQuality.name).apply()
|
||||||
|
|
||||||
var frontVideoResIndex: Int
|
var frontVideoResIndex: Int
|
||||||
get() = prefs.getInt(FRONT_VIDEO_RESOLUTION_INDEX, 0)
|
get() = prefs.getInt(FRONT_VIDEO_RESOLUTION_INDEX, 0)
|
||||||
set(frontVideoResIndex) = prefs.edit().putInt(FRONT_VIDEO_RESOLUTION_INDEX, frontVideoResIndex).apply()
|
set(frontVideoResIndex) = prefs.edit().putInt(FRONT_VIDEO_RESOLUTION_INDEX, frontVideoResIndex).apply()
|
||||||
|
|
|
@ -15,6 +15,8 @@ const val FLASHLIGHT_STATE = "flashlight_state"
|
||||||
const val INIT_PHOTO_MODE = "init_photo_mode"
|
const val INIT_PHOTO_MODE = "init_photo_mode"
|
||||||
const val BACK_PHOTO_RESOLUTION_INDEX = "back_photo_resolution_index_2"
|
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_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_PHOTO_RESOLUTION_INDEX = "front_photo_resolution_index_2"
|
||||||
const val FRONT_VIDEO_RESOLUTION_INDEX = "front_video_resolution_index_2"
|
const val FRONT_VIDEO_RESOLUTION_INDEX = "front_video_resolution_index_2"
|
||||||
const val KEEP_SETTINGS_VISIBLE = "keep_settings_visible"
|
const val KEEP_SETTINGS_VISIBLE = "keep_settings_visible"
|
||||||
|
|
|
@ -0,0 +1,89 @@
|
||||||
|
package com.simplemobiletools.camera.helpers
|
||||||
|
|
||||||
|
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.Log
|
||||||
|
import android.util.Size
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.camera.core.CameraSelector
|
||||||
|
import androidx.camera.video.Quality
|
||||||
|
import com.simplemobiletools.camera.extensions.config
|
||||||
|
import com.simplemobiletools.camera.extensions.toCameraXQuality
|
||||||
|
import com.simplemobiletools.camera.models.CameraSelectorImageQualities
|
||||||
|
import com.simplemobiletools.camera.models.CameraSelectorVideoQualities
|
||||||
|
import com.simplemobiletools.camera.models.MySize
|
||||||
|
|
||||||
|
class ImageQualityManager(
|
||||||
|
activity: AppCompatActivity,
|
||||||
|
) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "ImageQualityManager"
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val cameraManager = activity.getSystemService(Context.CAMERA_SERVICE) as CameraManager
|
||||||
|
private val config = activity.config
|
||||||
|
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))
|
||||||
|
Log.i(TAG, "initQualities: imageSizes=$imageSizes")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Camera ID=$cameraId is not supported", 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
|
||||||
|
} else {
|
||||||
|
CameraSelector.DEFAULT_BACK_CAMERA
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getUserSelectedResolution(cameraSelector: CameraSelector): Size? {
|
||||||
|
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}
|
||||||
|
.distinctBy { it.pixels }
|
||||||
|
.map { Size(it.width, it.height) }
|
||||||
|
.also {
|
||||||
|
Log.i(TAG, "Resolutions: $it, index=$index")
|
||||||
|
}
|
||||||
|
.getOrNull(index).also {
|
||||||
|
Log.i(TAG, "getUserSelectedResolution: $it, index=$index")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSupportedResolutions(cameraSelector: CameraSelector): List<MySize> {
|
||||||
|
return imageQualities.filter { it.camSelector == cameraSelector }
|
||||||
|
.flatMap { it.qualities }
|
||||||
|
.sortedByDescending { it.pixels }
|
||||||
|
.distinctBy { it.pixels }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
package com.simplemobiletools.camera.helpers
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.camera.core.Camera
|
||||||
|
import androidx.camera.core.CameraSelector
|
||||||
|
import androidx.camera.lifecycle.ProcessCameraProvider
|
||||||
|
import androidx.camera.video.Quality
|
||||||
|
import androidx.camera.video.QualitySelector
|
||||||
|
import com.simplemobiletools.camera.extensions.toCameraXQuality
|
||||||
|
import com.simplemobiletools.camera.extensions.toVideoQuality
|
||||||
|
import com.simplemobiletools.camera.models.CameraSelectorVideoQualities
|
||||||
|
import com.simplemobiletools.camera.models.VideoQuality
|
||||||
|
|
||||||
|
class VideoQualityManager(private val config: Config) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "VideoQualityHelper"
|
||||||
|
private val QUALITIES = listOf(Quality.UHD, Quality.FHD, Quality.HD, Quality.SD)
|
||||||
|
private val CAMERA_SELECTORS = arrayOf(CameraSelector.DEFAULT_BACK_CAMERA, CameraSelector.DEFAULT_FRONT_CAMERA)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val videoQualities = mutableListOf<CameraSelectorVideoQualities>()
|
||||||
|
|
||||||
|
fun initSupportedQualities(
|
||||||
|
cameraProvider: ProcessCameraProvider,
|
||||||
|
camera: Camera,
|
||||||
|
) {
|
||||||
|
if (videoQualities.isEmpty()) {
|
||||||
|
for (camSelector in CAMERA_SELECTORS) {
|
||||||
|
try {
|
||||||
|
if (cameraProvider.hasCamera(camSelector)) {
|
||||||
|
QualitySelector.getSupportedQualities(camera.cameraInfo)
|
||||||
|
.filter(QUALITIES::contains)
|
||||||
|
.also { allQualities ->
|
||||||
|
val qualities = allQualities.map { it.toVideoQuality() }
|
||||||
|
videoQualities.add(CameraSelectorVideoQualities(camSelector, qualities))
|
||||||
|
}
|
||||||
|
Log.i(TAG, "bindCameraUseCases: videoQualities=$videoQualities")
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Camera Face $camSelector is not supported", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getUserSelectedQuality(cameraSelector: CameraSelector): Quality {
|
||||||
|
return if (cameraSelector == CameraSelector.DEFAULT_FRONT_CAMERA) {
|
||||||
|
config.frontVideoQuality.toCameraXQuality()
|
||||||
|
} else {
|
||||||
|
config.backVideoQuality.toCameraXQuality()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSupportedQualities(cameraSelector: CameraSelector): List<VideoQuality> {
|
||||||
|
return videoQualities.filter { it.camSelector == cameraSelector }
|
||||||
|
.flatMap { it.qualities }
|
||||||
|
.sortedByDescending { it.pixels }
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,13 +1,9 @@
|
||||||
package com.simplemobiletools.camera.implementations
|
package com.simplemobiletools.camera.implementations
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.ContentValues
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.hardware.SensorManager
|
import android.hardware.SensorManager
|
||||||
import android.hardware.display.DisplayManager
|
import android.hardware.display.DisplayManager
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Environment
|
|
||||||
import android.provider.MediaStore
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.Display
|
import android.view.Display
|
||||||
import android.view.GestureDetector
|
import android.view.GestureDetector
|
||||||
|
@ -36,14 +32,7 @@ import androidx.camera.core.ImageCaptureException
|
||||||
import androidx.camera.core.Preview
|
import androidx.camera.core.Preview
|
||||||
import androidx.camera.core.UseCase
|
import androidx.camera.core.UseCase
|
||||||
import androidx.camera.lifecycle.ProcessCameraProvider
|
import androidx.camera.lifecycle.ProcessCameraProvider
|
||||||
import androidx.camera.video.FileDescriptorOutputOptions
|
import androidx.camera.video.*
|
||||||
import androidx.camera.video.MediaStoreOutputOptions
|
|
||||||
import androidx.camera.video.Quality
|
|
||||||
import androidx.camera.video.QualitySelector
|
|
||||||
import androidx.camera.video.Recorder
|
|
||||||
import androidx.camera.video.Recording
|
|
||||||
import androidx.camera.video.VideoCapture
|
|
||||||
import androidx.camera.video.VideoRecordEvent
|
|
||||||
import androidx.camera.view.PreviewView
|
import androidx.camera.view.PreviewView
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.view.doOnLayout
|
import androidx.core.view.doOnLayout
|
||||||
|
@ -52,23 +41,12 @@ import androidx.lifecycle.LifecycleOwner
|
||||||
import androidx.window.layout.WindowMetricsCalculator
|
import androidx.window.layout.WindowMetricsCalculator
|
||||||
import com.bumptech.glide.load.ImageHeaderParser.UNKNOWN_ORIENTATION
|
import com.bumptech.glide.load.ImageHeaderParser.UNKNOWN_ORIENTATION
|
||||||
import com.simplemobiletools.camera.R
|
import com.simplemobiletools.camera.R
|
||||||
import com.simplemobiletools.camera.extensions.config
|
import com.simplemobiletools.camera.dialogs.ChangeResolutionDialogX
|
||||||
import com.simplemobiletools.camera.extensions.getRandomMediaName
|
import com.simplemobiletools.camera.extensions.*
|
||||||
import com.simplemobiletools.camera.extensions.toAppFlashMode
|
import com.simplemobiletools.camera.helpers.*
|
||||||
import com.simplemobiletools.camera.extensions.toCameraSelector
|
|
||||||
import com.simplemobiletools.camera.extensions.toLensFacing
|
|
||||||
import com.simplemobiletools.camera.helpers.CameraErrorHandler
|
|
||||||
import com.simplemobiletools.camera.helpers.MediaOutputHelper
|
|
||||||
import com.simplemobiletools.camera.helpers.MediaSoundHelper
|
|
||||||
import com.simplemobiletools.camera.helpers.PinchToZoomOnScaleGestureListener
|
|
||||||
import com.simplemobiletools.camera.interfaces.MyPreview
|
import com.simplemobiletools.camera.interfaces.MyPreview
|
||||||
import com.simplemobiletools.camera.models.MediaOutput
|
import com.simplemobiletools.camera.models.MediaOutput
|
||||||
import com.simplemobiletools.commons.extensions.hasPermission
|
|
||||||
import com.simplemobiletools.commons.extensions.toast
|
import com.simplemobiletools.commons.extensions.toast
|
||||||
import com.simplemobiletools.commons.helpers.PERMISSION_RECORD_AUDIO
|
|
||||||
import java.text.SimpleDateFormat
|
|
||||||
import java.util.Date
|
|
||||||
import java.util.Locale
|
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
@ -98,6 +76,8 @@ class CameraXPreview(
|
||||||
private val displayManager = activity.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
|
private val displayManager = activity.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
|
||||||
private val mediaSoundHelper = MediaSoundHelper()
|
private val mediaSoundHelper = MediaSoundHelper()
|
||||||
private val windowMetricsCalculator = WindowMetricsCalculator.getOrCreate()
|
private val windowMetricsCalculator = WindowMetricsCalculator.getOrCreate()
|
||||||
|
private val videoQualityManager = VideoQualityManager(config)
|
||||||
|
private val imageQualityManager = ImageQualityManager(activity)
|
||||||
|
|
||||||
private val orientationEventListener = object : OrientationEventListener(activity, SensorManager.SENSOR_DELAY_NORMAL) {
|
private val orientationEventListener = object : OrientationEventListener(activity, SensorManager.SENSOR_DELAY_NORMAL) {
|
||||||
@SuppressLint("RestrictedApi")
|
@SuppressLint("RestrictedApi")
|
||||||
|
@ -146,6 +126,7 @@ class CameraXPreview(
|
||||||
|
|
||||||
private fun startCamera(switching: Boolean = false) {
|
private fun startCamera(switching: Boolean = false) {
|
||||||
Log.i(TAG, "startCamera: ")
|
Log.i(TAG, "startCamera: ")
|
||||||
|
imageQualityManager.initSupportedQualities()
|
||||||
val cameraProviderFuture = ProcessCameraProvider.getInstance(activity)
|
val cameraProviderFuture = ProcessCameraProvider.getInstance(activity)
|
||||||
cameraProviderFuture.addListener({
|
cameraProviderFuture.addListener({
|
||||||
try {
|
try {
|
||||||
|
@ -163,7 +144,12 @@ class CameraXPreview(
|
||||||
private fun bindCameraUseCases() {
|
private fun bindCameraUseCases() {
|
||||||
val cameraProvider = cameraProvider ?: throw IllegalStateException("Camera initialization failed.")
|
val cameraProvider = cameraProvider ?: throw IllegalStateException("Camera initialization failed.")
|
||||||
val metrics = windowMetricsCalculator.computeCurrentWindowMetrics(activity).bounds
|
val metrics = windowMetricsCalculator.computeCurrentWindowMetrics(activity).bounds
|
||||||
val aspectRatio = aspectRatio(metrics.width(), metrics.height())
|
val aspectRatio = if (isPhotoCapture) {
|
||||||
|
aspectRatio(metrics.width(), metrics.height())
|
||||||
|
} else {
|
||||||
|
val selectedQuality = videoQualityManager.getUserSelectedQuality(cameraSelector)
|
||||||
|
selectedQuality.getAspectRatio()
|
||||||
|
}
|
||||||
val rotation = previewView.display.rotation
|
val rotation = previewView.display.rotation
|
||||||
|
|
||||||
preview = buildPreview(aspectRatio, rotation)
|
preview = buildPreview(aspectRatio, rotation)
|
||||||
|
@ -174,7 +160,10 @@ class CameraXPreview(
|
||||||
cameraSelector,
|
cameraSelector,
|
||||||
preview,
|
preview,
|
||||||
captureUseCase,
|
captureUseCase,
|
||||||
)
|
).also {
|
||||||
|
videoQualityManager.initSupportedQualities(cameraProvider, it)
|
||||||
|
}
|
||||||
|
|
||||||
preview?.setSurfaceProvider(previewView.surfaceProvider)
|
preview?.setSurfaceProvider(previewView.surfaceProvider)
|
||||||
setupZoomAndFocus()
|
setupZoomAndFocus()
|
||||||
}
|
}
|
||||||
|
@ -220,22 +209,30 @@ class CameraXPreview(
|
||||||
.setCaptureMode(CAPTURE_MODE_MAXIMIZE_QUALITY)
|
.setCaptureMode(CAPTURE_MODE_MAXIMIZE_QUALITY)
|
||||||
.setFlashMode(flashMode)
|
.setFlashMode(flashMode)
|
||||||
.setJpegQuality(config.photoQuality)
|
.setJpegQuality(config.photoQuality)
|
||||||
.setTargetAspectRatio(aspectRatio)
|
|
||||||
.setTargetRotation(rotation)
|
.setTargetRotation(rotation)
|
||||||
|
.apply {
|
||||||
|
imageQualityManager.getUserSelectedResolution(cameraSelector)?.let { resolution ->
|
||||||
|
Log.i(TAG, "buildImageCapture: resolution=$resolution")
|
||||||
|
setTargetResolution(resolution)
|
||||||
|
} ?: setTargetAspectRatio(aspectRatio)
|
||||||
|
}
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildPreview(aspectRatio: Int, rotation: Int): Preview {
|
private fun buildPreview(aspectRatio: Int, rotation: Int): Preview {
|
||||||
return Preview.Builder()
|
return Preview.Builder()
|
||||||
.setTargetAspectRatio(aspectRatio)
|
|
||||||
.setTargetRotation(rotation)
|
.setTargetRotation(rotation)
|
||||||
|
.setTargetAspectRatio(aspectRatio)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildVideoCapture(): VideoCapture<Recorder> {
|
private fun buildVideoCapture(): VideoCapture<Recorder> {
|
||||||
|
val qualitySelector = QualitySelector.from(
|
||||||
|
videoQualityManager.getUserSelectedQuality(cameraSelector),
|
||||||
|
FallbackStrategy.lowerQualityOrHigherThan(Quality.SD),
|
||||||
|
)
|
||||||
val recorder = Recorder.Builder()
|
val recorder = Recorder.Builder()
|
||||||
//TODO: user control for quality
|
.setQualitySelector(qualitySelector)
|
||||||
.setQualitySelector(QualitySelector.from(Quality.FHD))
|
|
||||||
.build()
|
.build()
|
||||||
return VideoCapture.withOutput(recorder)
|
return VideoCapture.withOutput(recorder)
|
||||||
}
|
}
|
||||||
|
@ -305,7 +302,18 @@ class CameraXPreview(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showChangeResolutionDialog() {
|
override fun showChangeResolutionDialog() {
|
||||||
|
val oldQuality = videoQualityManager.getUserSelectedQuality(cameraSelector)
|
||||||
|
ChangeResolutionDialogX(
|
||||||
|
activity,
|
||||||
|
isFrontCameraInUse(),
|
||||||
|
imageQualityManager.getSupportedResolutions(cameraSelector),
|
||||||
|
videoQualityManager.getSupportedQualities(cameraSelector)
|
||||||
|
) {
|
||||||
|
if (oldQuality != videoQualityManager.getUserSelectedQuality(cameraSelector)) {
|
||||||
|
currentRecording?.stop()
|
||||||
|
}
|
||||||
|
startCamera()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toggleFrontBackCamera() {
|
override fun toggleFrontBackCamera() {
|
||||||
|
@ -344,7 +352,6 @@ class CameraXPreview(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun tryTakePicture() {
|
override fun tryTakePicture() {
|
||||||
Log.i(TAG, "captureImage: ")
|
|
||||||
val imageCapture = imageCapture ?: throw IllegalStateException("Camera initialization failed.")
|
val imageCapture = imageCapture ?: throw IllegalStateException("Camera initialization failed.")
|
||||||
|
|
||||||
val metadata = Metadata().apply {
|
val metadata = Metadata().apply {
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
package com.simplemobiletools.camera.models
|
||||||
|
|
||||||
|
import androidx.camera.core.CameraSelector
|
||||||
|
|
||||||
|
data class CameraSelectorImageQualities(
|
||||||
|
val camSelector: CameraSelector,
|
||||||
|
val qualities: List<MySize>,
|
||||||
|
)
|
|
@ -0,0 +1,8 @@
|
||||||
|
package com.simplemobiletools.camera.models
|
||||||
|
|
||||||
|
import androidx.camera.core.CameraSelector
|
||||||
|
|
||||||
|
data class CameraSelectorVideoQualities(
|
||||||
|
val camSelector: CameraSelector,
|
||||||
|
val qualities: List<VideoQuality>,
|
||||||
|
)
|
|
@ -6,6 +6,7 @@ import com.simplemobiletools.camera.R
|
||||||
|
|
||||||
data class MySize(val width: Int, val height: Int) {
|
data class MySize(val width: Int, val height: Int) {
|
||||||
val ratio = width / height.toFloat()
|
val ratio = width / height.toFloat()
|
||||||
|
val pixels: Int = width * height
|
||||||
fun isSixteenToNine() = ratio == 16 / 9f
|
fun isSixteenToNine() = ratio == 16 / 9f
|
||||||
|
|
||||||
private fun isFiveToThree() = ratio == 5 / 3f
|
private fun isFiveToThree() = ratio == 5 / 3f
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
package com.simplemobiletools.camera.models
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.simplemobiletools.camera.R
|
||||||
|
|
||||||
|
enum class VideoQuality(val width: Int, val height: Int) {
|
||||||
|
UHD(3840, 2160),
|
||||||
|
FHD(1920, 1080),
|
||||||
|
HD(1280, 720),
|
||||||
|
SD(720, 480);
|
||||||
|
|
||||||
|
|
||||||
|
val pixels: Int = width * height
|
||||||
|
|
||||||
|
val megaPixels: String = String.format("%.1f", (width * height.toFloat()) / VideoQuality.ONE_MEGA_PIXELS)
|
||||||
|
|
||||||
|
val ratio = width / height.toFloat()
|
||||||
|
|
||||||
|
private fun isSixteenToNine() = ratio == 16 / 9f
|
||||||
|
|
||||||
|
private fun isFiveToThree() = ratio == 5 / 3f
|
||||||
|
|
||||||
|
private fun isFourToThree() = ratio == 4 / 3f
|
||||||
|
|
||||||
|
private fun isTwoToOne() = ratio == 2f
|
||||||
|
|
||||||
|
private fun isThreeToFour() = ratio == 3 / 4f
|
||||||
|
|
||||||
|
private fun isThreeToTwo() = ratio == 3 / 2f
|
||||||
|
|
||||||
|
private fun isSixToFive() = ratio == 6 / 5f
|
||||||
|
|
||||||
|
private fun isNineteenToNine() = ratio == 19 / 9f
|
||||||
|
|
||||||
|
private fun isNineteenToEight() = ratio == 19 / 8f
|
||||||
|
|
||||||
|
private fun isOneNineToOne() = ratio == 1.9f
|
||||||
|
|
||||||
|
private fun isSquare() = width == height
|
||||||
|
|
||||||
|
fun getAspectRatio(context: Context) = when {
|
||||||
|
isSixteenToNine() -> "16:9"
|
||||||
|
isFiveToThree() -> "5:3"
|
||||||
|
isFourToThree() -> "4:3"
|
||||||
|
isThreeToFour() -> "3:4"
|
||||||
|
isThreeToTwo() -> "3:2"
|
||||||
|
isSixToFive() -> "6:5"
|
||||||
|
isOneNineToOne() -> "1.9:1"
|
||||||
|
isNineteenToNine() -> "19:9"
|
||||||
|
isNineteenToEight() -> "19:8"
|
||||||
|
isSquare() -> "1:1"
|
||||||
|
isTwoToOne() -> "2:1"
|
||||||
|
else -> context.resources.getString(R.string.other)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val ONE_MEGA_PIXELS = 1000000
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue