From 61c2a32e00a1bc66dbef79921c1164bf4e50620f Mon Sep 17 00:00:00 2001 From: MHShetty Date: Fri, 7 Jun 2024 23:31:59 +0530 Subject: [PATCH 1/6] Add constants and preference variable (for frame rate per second setting) --- .../java/app/grapheneos/camera/CamConfig.kt | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/app/grapheneos/camera/CamConfig.kt b/app/src/main/java/app/grapheneos/camera/CamConfig.kt index 128205f5..8e16d083 100644 --- a/app/src/main/java/app/grapheneos/camera/CamConfig.kt +++ b/app/src/main/java/app/grapheneos/camera/CamConfig.kt @@ -7,6 +7,7 @@ import android.net.Uri import android.os.Build import android.provider.MediaStore import android.util.Log +import android.util.Range import android.util.Size import android.view.MotionEvent import android.view.View @@ -86,6 +87,7 @@ class CamConfig(private val mActivity: MainActivity) { const val EMPHASIS_ON_QUALITY = "emphasis_on_quality" const val FOCUS_TIMEOUT = "focus_timeout" const val VIDEO_QUALITY = "video_quality" + const val VIDEO_FRAME_RATE = "video_frame_rate" const val ASPECT_RATIO = "aspect_ratio" const val INCLUDE_AUDIO = "include_audio" const val ENABLE_EIS = "enable_eis" @@ -124,6 +126,8 @@ class CamConfig(private val mActivity: MainActivity) { val VIDEO_QUALITY = Quality.HIGHEST + val VIDEO_FRAME_RATE = Range(30, 30) + const val SELF_ILLUMINATION = false const val GEO_TAGGING = false @@ -357,6 +361,36 @@ class CamConfig(private val mActivity: MainActivity) { return "${SettingValues.Key.VIDEO_QUALITY}_$pf" } + var videoFrameRate: Range = SettingValues.Default.VIDEO_FRAME_RATE + get() { + return if (modePref.contains(videoFrameRateKey)) { + mActivity.settingsDialog.titleToFrameRateRange( + modePref.getString(videoFrameRateKey, "")!! + ) + + } else { + SettingValues.Default.VIDEO_FRAME_RATE + } + } + set(value) { + modePref.edit { + putString(videoFrameRateKey, mActivity.settingsDialog.getTitleForFrameRateRange(value)) + } + + field = value + } + + val videoFrameRateKey : String + get() { + val pf = if (lensFacing == CameraSelector.LENS_FACING_FRONT) { + "FRONT" + } else { + "BACK" + } + + return "${SettingValues.Key.VIDEO_FRAME_RATE}_$pf" + } + var flashMode: Int get() = if (imageCapture != null) imageCapture!!.flashMode else SettingValues.Default.FLASH_MODE @@ -711,7 +745,7 @@ class CamConfig(private val mActivity: MainActivity) { } if (isVideoMode) { - mActivity.settingsDialog.reloadQualities() + mActivity.settingsDialog.reloadVideoSettings() } if (lensFacing == CameraSelector.LENS_FACING_FRONT) { From 7c6e5a0e9692d5605a2fb4e69e3e44282f60fe9f Mon Sep 17 00:00:00 2001 From: MHShetty Date: Fri, 7 Jun 2024 23:34:01 +0530 Subject: [PATCH 2/6] Add UI for frame rate per second setting (in quick settings dialog) --- app/src/main/res/layout/settings.xml | 33 ++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/app/src/main/res/layout/settings.xml b/app/src/main/res/layout/settings.xml index 2a0671f6..d6012ace 100644 --- a/app/src/main/res/layout/settings.xml +++ b/app/src/main/res/layout/settings.xml @@ -214,6 +214,39 @@ + + + + + + + + + + + Date: Tue, 13 Aug 2024 03:02:54 +0530 Subject: [PATCH 3/6] Add code to support UI of fps setting (show appropriate options in dropdown and selection of default fps) --- .../java/app/grapheneos/camera/CamConfig.kt | 6 +- .../camera/capturer/VideoCapturer.kt | 2 + .../grapheneos/camera/ui/SettingsDialog.kt | 176 ++++++++++++++---- 3 files changed, 142 insertions(+), 42 deletions(-) diff --git a/app/src/main/java/app/grapheneos/camera/CamConfig.kt b/app/src/main/java/app/grapheneos/camera/CamConfig.kt index 8e16d083..9cf66dcd 100644 --- a/app/src/main/java/app/grapheneos/camera/CamConfig.kt +++ b/app/src/main/java/app/grapheneos/camera/CamConfig.kt @@ -41,6 +41,7 @@ import androidx.core.content.ContextCompat import app.grapheneos.camera.analyzer.QRAnalyzer import app.grapheneos.camera.ktx.markAs16by9Layout import app.grapheneos.camera.ktx.markAs4by3Layout +import app.grapheneos.camera.ui.SettingsDialog import app.grapheneos.camera.ui.activities.CaptureActivity import app.grapheneos.camera.ui.activities.MainActivity import app.grapheneos.camera.ui.activities.MoreSettings @@ -332,7 +333,7 @@ class CamConfig(private val mActivity: MainActivity) { var videoQuality: Quality = SettingValues.Default.VIDEO_QUALITY get() { return if (modePref.contains(videoQualityKey)) { - mActivity.settingsDialog.titleToQuality( + SettingsDialog.titleToQuality( modePref.getString(videoQualityKey, "")!! ) } else { @@ -364,10 +365,9 @@ class CamConfig(private val mActivity: MainActivity) { var videoFrameRate: Range = SettingValues.Default.VIDEO_FRAME_RATE get() { return if (modePref.contains(videoFrameRateKey)) { - mActivity.settingsDialog.titleToFrameRateRange( + SettingsDialog.titleToFrameRateRange( modePref.getString(videoFrameRateKey, "")!! ) - } else { SettingValues.Default.VIDEO_FRAME_RATE } diff --git a/app/src/main/java/app/grapheneos/camera/capturer/VideoCapturer.kt b/app/src/main/java/app/grapheneos/camera/capturer/VideoCapturer.kt index 9a7443fb..706f3c01 100644 --- a/app/src/main/java/app/grapheneos/camera/capturer/VideoCapturer.kt +++ b/app/src/main/java/app/grapheneos/camera/capturer/VideoCapturer.kt @@ -284,6 +284,7 @@ class VideoCapturer(private val mActivity: MainActivity) { animator.start() mActivity.settingsDialog.videoQualitySpinner.isEnabled = false + mActivity.settingsDialog.videoFrameRateSpinner.isEnabled = false mActivity.settingsDialog.enableEISToggle.isEnabled = false mActivity.flipCamIcon.setImageResource(R.drawable.pause) @@ -331,6 +332,7 @@ class VideoCapturer(private val mActivity: MainActivity) { mActivity.flipCamIcon.setImageResource(R.drawable.flip_camera) mActivity.settingsDialog.videoQualitySpinner.isEnabled = true + mActivity.settingsDialog.videoFrameRateSpinner.isEnabled = true mActivity.settingsDialog.enableEISToggle.isEnabled = true if (mActivity !is VideoCaptureActivity) { diff --git a/app/src/main/java/app/grapheneos/camera/ui/SettingsDialog.kt b/app/src/main/java/app/grapheneos/camera/ui/SettingsDialog.kt index 7a09d5c1..5677c96e 100644 --- a/app/src/main/java/app/grapheneos/camera/ui/SettingsDialog.kt +++ b/app/src/main/java/app/grapheneos/camera/ui/SettingsDialog.kt @@ -12,6 +12,7 @@ import android.os.Handler import android.os.Looper import android.provider.Settings import android.util.Log +import android.util.Range import android.view.MotionEvent import android.view.View import android.view.ViewGroup @@ -61,8 +62,13 @@ class SettingsDialog(val mActivity: MainActivity, themedContext: Context) : private var aRToggle: ToggleButton var torchToggle: ToggleButton private var gridToggle: ImageView + var videoQualitySpinner: Spinner - private lateinit var vQAdapter: ArrayAdapter + var videoFrameRateSpinner: Spinner + + private lateinit var videoQualityAdapter: ArrayAdapter + private lateinit var videoFrameRateAdapter: ArrayAdapter + private var focusTimeoutSpinner: Spinner private var timerSpinner: Spinner @@ -84,6 +90,7 @@ class SettingsDialog(val mActivity: MainActivity, themedContext: Context) : private var enableEISSetting: View private var selfIlluminationSetting: View private var videoQualitySetting: View + private var videoFrameRateSetting: LinearLayout private var timerSetting: View var settingsFrame: View @@ -226,19 +233,35 @@ class SettingsDialog(val mActivity: MainActivity, themedContext: Context) : videoQualitySpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { override fun onItemSelected( - p0: AdapterView<*>?, - p1: View?, + parent: AdapterView<*>?, + view: View?, position: Int, - p3: Long + id: Long ) { - - val choice = vQAdapter.getItem(position) as String + val choice = videoQualityAdapter.getItem(position) as String updateVideoQuality(choice) } - override fun onNothingSelected(p0: AdapterView<*>?) {} + override fun onNothingSelected(parent: AdapterView<*>?) {} } + videoFrameRateSpinner = binding.videoFrameRateSpinner + + videoFrameRateSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + override fun onItemSelected( + parent: AdapterView<*>?, + view: View?, + position: Int, + id: Long + ) { + val choice = videoFrameRateAdapter.getItem(position) as String + updateVideoFrameRate(choice) + } + + override fun onNothingSelected(parent: AdapterView<*>?) {} + + } + qRadio = binding.qualityRadio lRadio = binding.latencyRadio @@ -333,6 +356,7 @@ class SettingsDialog(val mActivity: MainActivity, themedContext: Context) : enableEISSetting = binding.enableEisSetting selfIlluminationSetting = binding.selfIlluminationSetting videoQualitySetting = binding.videoQualitySetting + videoFrameRateSetting = binding.videoFrameRateSetting timerSetting = binding.timerSetting includeAudioToggle = binding.includeAudioSwitch @@ -447,17 +471,18 @@ class SettingsDialog(val mActivity: MainActivity, themedContext: Context) : @androidx.camera.camera2.interop.ExperimentalCamera2Interop if (camConfig.isVideoMode) { includeAudioSetting.visibility = View.VISIBLE - enableEISSetting.visibility = View.GONE videoQualitySetting.visibility = View.VISIBLE enableEISSetting.visibility = if (camConfig.isVideoStabilizationSupported()) { View.VISIBLE } else { View.GONE } + videoFrameRateSetting.visibility = View.VISIBLE } else { includeAudioSetting.visibility = View.GONE enableEISSetting.visibility = View.GONE videoQualitySetting.visibility = View.GONE + videoFrameRateSetting.visibility = View.GONE } selfIlluminationSetting.visibility = @@ -510,21 +535,23 @@ class SettingsDialog(val mActivity: MainActivity, themedContext: Context) : if (resCam) { camConfig.startCamera(true) } else { - videoQualitySpinner.setSelection(getAvailableQTitles().indexOf(choice)) + videoQualitySpinner.setSelection(getAvailableQualityTitles().indexOf(choice)) } } - fun titleToQuality(title: String): Quality { - return when (title) { - "2160p (UHD)" -> Quality.UHD - "1080p (FHD)" -> Quality.FHD - "720p (HD)" -> Quality.HD - "480p (SD)" -> Quality.SD - else -> { - Log.e("TAG", "Unknown quality: $title") - Quality.SD - } + fun updateVideoFrameRate(choice: String, restartCamera: Boolean = true) { + + val videoFrameRate = titleToFrameRateRange(choice) + + if (videoFrameRate == camConfig.videoFrameRate) return + + camConfig.videoFrameRate = videoFrameRate + + if (restartCamera) { + camConfig.startCamera(true) + } else { + videoFrameRateSpinner.setSelection(videoFrameRateAdapter.getPosition(choice)) } } @@ -717,27 +744,31 @@ class SettingsDialog(val mActivity: MainActivity, themedContext: Context) : return Recorder.getVideoCapabilities(cameraInfo).getSupportedQualities(DynamicRange.SDR) } - private fun getAvailableQTitles(): List { + private fun getAvailableVideoFrameRates(): List> { + val resSet = camConfig.camera?.cameraInfo?.supportedFrameRateRanges ?: Collections.emptySet() + // Individual fps -> Ranged fps (sorted by lower value of range and then upper for each lower value) + val resList = resSet.sortedWith(compareBy> { it.lower != it.upper }.thenBy { it.lower }.thenBy { it.upper }) + return resList + } + + private fun getAvailableQualityTitles(): List { val titles = arrayListOf() getAvailableQualities().forEach { - titles.add(getTitleFor(it)) + titles.add(getTitleForQuality(it)) } return titles } - private fun getTitleFor(quality: Quality): String { - return when (quality) { - Quality.UHD -> "2160p (UHD)" - Quality.FHD -> "1080p (FHD)" - Quality.HD -> "720p (HD)" - Quality.SD -> "480p (SD)" - else -> { - Log.i("TAG", "Unknown constant: $quality") - "Unknown" - } + private fun getAvailableFRTitles(): List { + val titles = arrayListOf() + + getAvailableVideoFrameRates().forEach { + titles.add(getTitleForFrameRateRange(it)) } + + return titles } fun updateGridToggleUI() { @@ -788,24 +819,91 @@ class SettingsDialog(val mActivity: MainActivity, themedContext: Context) : slideDialogDown() } - fun reloadQualities() { + fun reloadVideoSettings() { - val titles = getAvailableQTitles() + val qualityTitles = getAvailableQualityTitles() + val frameRateTitles = getAvailableFRTitles() - vQAdapter = ArrayAdapter( + videoQualityAdapter = ArrayAdapter( mActivity, android.R.layout.simple_spinner_item, - titles + qualityTitles ) - vQAdapter.setDropDownViewResource( + videoQualityAdapter.setDropDownViewResource( android.R.layout.simple_spinner_dropdown_item ) - videoQualitySpinner.adapter = vQAdapter + videoQualitySpinner.adapter = videoQualityAdapter - if (camConfig.videoQuality != Quality.HIGHEST) { - videoQualitySpinner.setSelection(titles.indexOf(getTitleFor(camConfig.videoQuality))) + videoFrameRateAdapter = ArrayAdapter( + mActivity, + android.R.layout.simple_spinner_item, + frameRateTitles + ) + + videoFrameRateAdapter.setDropDownViewResource( + android.R.layout.simple_spinner_dropdown_item + ) + + videoFrameRateSpinner.adapter = videoFrameRateAdapter + + videoFrameRateSpinner.setSelection(videoFrameRateAdapter.getPosition(getTitleForFrameRateRange(camConfig.videoFrameRate))) + + if (camConfig.videoQuality != CamConfig.SettingValues.Default.VIDEO_QUALITY) { + videoQualitySpinner.setSelection(videoQualityAdapter.getPosition(getTitleForQuality(camConfig.videoQuality))) + } + } + + companion object { + fun titleToQuality(title: String): Quality { + return when (title) { + "2160p (UHD)" -> Quality.UHD + "1080p (FHD)" -> Quality.FHD + "720p (HD)" -> Quality.HD + "480p (SD)" -> Quality.SD + else -> { + Log.e("TAG", "Unknown quality: $title") + Quality.SD + } + } + } + + fun titleToFrameRateRange(title: String): Range { + val titleWithoutFps = title.dropLast(4) + + if (titleWithoutFps.contains("-")) { + val lUArr = titleWithoutFps.split("-") + + val lower = lUArr[0].dropLast(1).toInt() + val upper = lUArr[1].drop(1).toInt() + + return Range(lower, upper) + } else { + val fps = titleWithoutFps.toInt() + return Range(fps, fps) + } + } + } + + fun getTitleForFrameRateRange(range: Range) : String { + if (range.lower == range.upper) { + return "${range.lower} fps" + } else { + return "${range.lower} - ${range.upper} fps" + } + } + + private fun getTitleForQuality(quality: Quality): String { + return when (quality) { + Quality.UHD -> "2160p (UHD)" + Quality.FHD -> "1080p (FHD)" + Quality.HD -> "720p (HD)" + Quality.SD -> "480p (SD)" + else -> { + Log.i("TAG", "Unknown constant: $quality") + "Unknown" + } } } } From ddfe2740a9b58e912f6a7e79a0f21a30f92b4fbe Mon Sep 17 00:00:00 2001 From: MHShetty Date: Thu, 18 Jul 2024 23:16:15 +0530 Subject: [PATCH 4/6] Implement FPS functionality --- app/src/main/java/app/grapheneos/camera/CamConfig.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/app/grapheneos/camera/CamConfig.kt b/app/src/main/java/app/grapheneos/camera/CamConfig.kt index 9cf66dcd..cddc6e4a 100644 --- a/app/src/main/java/app/grapheneos/camera/CamConfig.kt +++ b/app/src/main/java/app/grapheneos/camera/CamConfig.kt @@ -1191,6 +1191,8 @@ class CamConfig(private val mActivity: MainActivity) { if (mActivity.camConfig.saveVideoAsPreviewed) videoCaptureBuilder.setMirrorMode(MirrorMode.MIRROR_MODE_ON_FRONT_ONLY) + videoCaptureBuilder.setTargetFrameRate(mActivity.camConfig.videoFrameRate) + videoCapture = videoCaptureBuilder.build() useCaseGroupBuilder.addUseCase(videoCapture!!) From d107c30c1f211b000ed6ce8f39ea1ed6a990e94f Mon Sep 17 00:00:00 2001 From: MHShetty Date: Fri, 19 Jul 2024 13:35:44 +0530 Subject: [PATCH 5/6] Handle cases when 30 fps is unavailable (and prevent crash when video mode is directly turned on) --- .../java/app/grapheneos/camera/CamConfig.kt | 25 +++++++++++++++++-- .../grapheneos/camera/ui/SettingsDialog.kt | 12 +++------ 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/app/grapheneos/camera/CamConfig.kt b/app/src/main/java/app/grapheneos/camera/CamConfig.kt index cddc6e4a..dd5670bf 100644 --- a/app/src/main/java/app/grapheneos/camera/CamConfig.kt +++ b/app/src/main/java/app/grapheneos/camera/CamConfig.kt @@ -369,7 +369,7 @@ class CamConfig(private val mActivity: MainActivity) { modePref.getString(videoFrameRateKey, "")!! ) } else { - SettingValues.Default.VIDEO_FRAME_RATE + defaultVideoFrameRate } } set(value) { @@ -391,6 +391,14 @@ class CamConfig(private val mActivity: MainActivity) { return "${SettingValues.Key.VIDEO_FRAME_RATE}_$pf" } + val defaultVideoFrameRate : Range + get() { + val availableFrameRates = getAvailableVideoFrameRates() + if (availableFrameRates.contains(SettingValues.Default.VIDEO_FRAME_RATE)) + return SettingValues.Default.VIDEO_FRAME_RATE + return availableFrameRates[0] + } + var flashMode: Int get() = if (imageCapture != null) imageCapture!!.flashMode else SettingValues.Default.FLASH_MODE @@ -785,7 +793,7 @@ class CamConfig(private val mActivity: MainActivity) { editor.putBoolean(SettingValues.Key.CAMERA_SOUNDS, SettingValues.Default.CAMERA_SOUNDS) } - // Note: This is a workaround to keep save image/video as previewed 'on' by + // Note: This is a workaround to keep save image/video as previewed 'on' by // default starting from v73 and 'off' by default for versions before that // // If its not a fresh install (before v73) @@ -991,6 +999,19 @@ class CamConfig(private val mActivity: MainActivity) { return cameraProvider!!.getCameraInfo(cameraSelector) } + fun getAvailableVideoFrameRates(): List> { + val resSet = getCurrentCameraInfo().supportedFrameRateRanges + + // Individual fps -> Ranged fps (sorted by lower value of range and then upper for each lower value) + val resList = resSet.sortedWith(compareBy> { it.lower != it.upper }.thenBy { it.lower }.thenBy { it.upper }) + + // If the supportedFrameRateRange list is somehow empty due to device/library implementation + // go with the most likely default rate + if (resList.isEmpty()) return listOf(SettingValues.Default.VIDEO_FRAME_RATE) + + return resList + } + fun toggleCameraSelector() { // Manually switch to the opposite lens facing diff --git a/app/src/main/java/app/grapheneos/camera/ui/SettingsDialog.kt b/app/src/main/java/app/grapheneos/camera/ui/SettingsDialog.kt index 5677c96e..245cc48d 100644 --- a/app/src/main/java/app/grapheneos/camera/ui/SettingsDialog.kt +++ b/app/src/main/java/app/grapheneos/camera/ui/SettingsDialog.kt @@ -744,12 +744,6 @@ class SettingsDialog(val mActivity: MainActivity, themedContext: Context) : return Recorder.getVideoCapabilities(cameraInfo).getSupportedQualities(DynamicRange.SDR) } - private fun getAvailableVideoFrameRates(): List> { - val resSet = camConfig.camera?.cameraInfo?.supportedFrameRateRanges ?: Collections.emptySet() - // Individual fps -> Ranged fps (sorted by lower value of range and then upper for each lower value) - val resList = resSet.sortedWith(compareBy> { it.lower != it.upper }.thenBy { it.lower }.thenBy { it.upper }) - return resList - } private fun getAvailableQualityTitles(): List { val titles = arrayListOf() @@ -761,10 +755,10 @@ class SettingsDialog(val mActivity: MainActivity, themedContext: Context) : return titles } - private fun getAvailableFRTitles(): List { + private fun getAvailableFrameRateTitles(): List { val titles = arrayListOf() - getAvailableVideoFrameRates().forEach { + camConfig.getAvailableVideoFrameRates().forEach { titles.add(getTitleForFrameRateRange(it)) } @@ -822,7 +816,7 @@ class SettingsDialog(val mActivity: MainActivity, themedContext: Context) : fun reloadVideoSettings() { val qualityTitles = getAvailableQualityTitles() - val frameRateTitles = getAvailableFRTitles() + val frameRateTitles = getAvailableFrameRateTitles() videoQualityAdapter = ArrayAdapter( mActivity, From 6ac1e56320810d144de17f1166e220a0a80b8fdc Mon Sep 17 00:00:00 2001 From: MHShetty Date: Thu, 18 Jul 2024 23:23:25 +0530 Subject: [PATCH 6/6] Add string resource (for frame rate per second setting) --- app/src/main/res/values/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9aa6ae1e..92f5ff8f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -193,4 +193,5 @@ The video\'s audio recording has been muted The video\'s audio recording has been unmuted + Video Frame Rate