fix: Fix 60 FPS crashing on some Samsungs (#2556)
* fix: Fix 60 FPS crash on Samsung by checking `CamcorderProfile.maxFps` * Log FPS clamp * Update CameraDeviceDetails.kt * Format code
This commit is contained in:
parent
3699ccde94
commit
478688529b
@ -1,12 +1,12 @@
|
|||||||
package com.mrousavy.camera.core
|
package com.mrousavy.camera.core
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.graphics.ImageFormat
|
import android.graphics.ImageFormat
|
||||||
import android.hardware.camera2.CameraCharacteristics
|
import android.hardware.camera2.CameraCharacteristics
|
||||||
import android.hardware.camera2.CameraExtensionCharacteristics
|
import android.hardware.camera2.CameraExtensionCharacteristics
|
||||||
import android.hardware.camera2.CameraManager
|
import android.hardware.camera2.CameraManager
|
||||||
import android.hardware.camera2.CameraMetadata
|
import android.hardware.camera2.CameraMetadata
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import android.util.Log
|
||||||
import android.util.Range
|
import android.util.Range
|
||||||
import android.util.Size
|
import android.util.Size
|
||||||
import com.facebook.react.bridge.Arguments
|
import com.facebook.react.bridge.Arguments
|
||||||
@ -22,11 +22,15 @@ import com.mrousavy.camera.types.LensFacing
|
|||||||
import com.mrousavy.camera.types.Orientation
|
import com.mrousavy.camera.types.Orientation
|
||||||
import com.mrousavy.camera.types.PixelFormat
|
import com.mrousavy.camera.types.PixelFormat
|
||||||
import com.mrousavy.camera.types.VideoStabilizationMode
|
import com.mrousavy.camera.types.VideoStabilizationMode
|
||||||
|
import com.mrousavy.camera.utils.CamcorderProfileUtils
|
||||||
import kotlin.math.atan2
|
import kotlin.math.atan2
|
||||||
import kotlin.math.sqrt
|
import kotlin.math.sqrt
|
||||||
|
|
||||||
@SuppressLint("InlinedApi")
|
|
||||||
class CameraDeviceDetails(private val cameraManager: CameraManager, val cameraId: String) {
|
class CameraDeviceDetails(private val cameraManager: CameraManager, val cameraId: String) {
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "CameraDeviceDetails"
|
||||||
|
}
|
||||||
|
|
||||||
val characteristics by lazy { cameraManager.getCameraCharacteristics(cameraId) }
|
val characteristics by lazy { cameraManager.getCameraCharacteristics(cameraId) }
|
||||||
val hardwareLevel by lazy { HardwareLevel.fromCameraCharacteristics(characteristics) }
|
val hardwareLevel by lazy { HardwareLevel.fromCameraCharacteristics(characteristics) }
|
||||||
val capabilities by lazy { characteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES) ?: IntArray(0) }
|
val capabilities by lazy { characteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES) ?: IntArray(0) }
|
||||||
@ -200,7 +204,15 @@ class CameraDeviceDetails(private val cameraManager: CameraManager, val cameraId
|
|||||||
|
|
||||||
videoSizes.forEach { videoSize ->
|
videoSizes.forEach { videoSize ->
|
||||||
val frameDuration = cameraConfig.getOutputMinFrameDuration(videoFormat, videoSize)
|
val frameDuration = cameraConfig.getOutputMinFrameDuration(videoFormat, videoSize)
|
||||||
val maxFps = (1.0 / (frameDuration.toDouble() / 1_000_000_000)).toInt()
|
var maxFps = (1.0 / (frameDuration.toDouble() / 1_000_000_000)).toInt()
|
||||||
|
val maxEncoderFps = CamcorderProfileUtils.getMaximumFps(cameraId, videoSize)
|
||||||
|
if (maxEncoderFps != null && maxEncoderFps < maxFps) {
|
||||||
|
Log.i(
|
||||||
|
TAG,
|
||||||
|
"Camera could do $maxFps FPS at $videoSize, but Media Encoder can only do $maxEncoderFps FPS. Clamping to $maxEncoderFps FPS..."
|
||||||
|
)
|
||||||
|
maxFps = maxEncoderFps
|
||||||
|
}
|
||||||
|
|
||||||
photoSizes.forEach { photoSize ->
|
photoSizes.forEach { photoSize ->
|
||||||
val map = buildFormatMap(photoSize, videoSize, Range(1, maxFps))
|
val map = buildFormatMap(photoSize, videoSize, Range(1, maxFps))
|
||||||
|
@ -1,39 +1,13 @@
|
|||||||
package com.mrousavy.camera.extensions
|
package com.mrousavy.camera.extensions
|
||||||
|
|
||||||
import android.hardware.camera2.CameraCharacteristics
|
import android.hardware.camera2.CameraCharacteristics
|
||||||
import android.media.CamcorderProfile
|
|
||||||
import android.os.Build
|
|
||||||
import android.util.Size
|
import android.util.Size
|
||||||
|
import com.mrousavy.camera.utils.CamcorderProfileUtils
|
||||||
private fun getMaximumVideoSize(cameraId: String): Size? {
|
|
||||||
try {
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
||||||
val profiles = CamcorderProfile.getAll(cameraId, CamcorderProfile.QUALITY_HIGH)
|
|
||||||
if (profiles != null) {
|
|
||||||
val largestProfile = profiles.videoProfiles.filterNotNull().maxByOrNull { it.width * it.height }
|
|
||||||
if (largestProfile != null) {
|
|
||||||
return Size(largestProfile.width, largestProfile.height)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val cameraIdInt = cameraId.toIntOrNull()
|
|
||||||
if (cameraIdInt != null) {
|
|
||||||
val profile = CamcorderProfile.get(cameraIdInt, CamcorderProfile.QUALITY_HIGH)
|
|
||||||
return Size(profile.videoFrameWidth, profile.videoFrameHeight)
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
} catch (e: Throwable) {
|
|
||||||
// some Samsung phones just crash when trying to get the CamcorderProfile. Only god knows why.
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun CameraCharacteristics.getVideoSizes(cameraId: String, format: Int): List<Size> {
|
fun CameraCharacteristics.getVideoSizes(cameraId: String, format: Int): List<Size> {
|
||||||
val config = this.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)!!
|
val config = this.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)!!
|
||||||
val sizes = config.getOutputSizes(format) ?: emptyArray()
|
val sizes = config.getOutputSizes(format) ?: emptyArray()
|
||||||
val maxVideoSize = getMaximumVideoSize(cameraId)
|
val maxVideoSize = CamcorderProfileUtils.getMaximumVideoSize(cameraId)
|
||||||
if (maxVideoSize != null) {
|
if (maxVideoSize != null) {
|
||||||
return sizes.filter { it.bigger <= maxVideoSize.bigger }
|
return sizes.filter { it.bigger <= maxVideoSize.bigger }
|
||||||
}
|
}
|
||||||
|
@ -4,9 +4,9 @@ import android.media.CamcorderProfile
|
|||||||
import android.media.MediaRecorder.VideoEncoder
|
import android.media.MediaRecorder.VideoEncoder
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.util.Size
|
|
||||||
import com.mrousavy.camera.core.RecordingSession
|
import com.mrousavy.camera.core.RecordingSession
|
||||||
import com.mrousavy.camera.types.VideoCodec
|
import com.mrousavy.camera.types.VideoCodec
|
||||||
|
import com.mrousavy.camera.utils.CamcorderProfileUtils
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|
||||||
data class RecommendedProfile(
|
data class RecommendedProfile(
|
||||||
@ -23,7 +23,7 @@ fun RecordingSession.getRecommendedBitRate(fps: Int, codec: VideoCodec, hdr: Boo
|
|||||||
val targetResolution = size
|
val targetResolution = size
|
||||||
val encoder = codec.toVideoEncoder()
|
val encoder = codec.toVideoEncoder()
|
||||||
val bitDepth = if (hdr) 10 else 8
|
val bitDepth = if (hdr) 10 else 8
|
||||||
val quality = findClosestCamcorderProfileQuality(cameraId, targetResolution)
|
val quality = CamcorderProfileUtils.findClosestCamcorderProfileQuality(cameraId, targetResolution, true)
|
||||||
Log.i("CamcorderProfile", "Closest matching CamcorderProfile: $quality")
|
Log.i("CamcorderProfile", "Closest matching CamcorderProfile: $quality")
|
||||||
|
|
||||||
var recommendedProfile: RecommendedProfile? = null
|
var recommendedProfile: RecommendedProfile? = null
|
||||||
@ -75,39 +75,3 @@ fun RecordingSession.getRecommendedBitRate(fps: Int, codec: VideoCodec, hdr: Boo
|
|||||||
}
|
}
|
||||||
return bitRate.toInt()
|
return bitRate.toInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getResolutionForCamcorderProfileQuality(camcorderProfile: Int): Int =
|
|
||||||
when (camcorderProfile) {
|
|
||||||
CamcorderProfile.QUALITY_QCIF -> 176 * 144
|
|
||||||
CamcorderProfile.QUALITY_QVGA -> 320 * 240
|
|
||||||
CamcorderProfile.QUALITY_CIF -> 352 * 288
|
|
||||||
CamcorderProfile.QUALITY_VGA -> 640 * 480
|
|
||||||
CamcorderProfile.QUALITY_480P -> 720 * 480
|
|
||||||
CamcorderProfile.QUALITY_720P -> 1280 * 720
|
|
||||||
CamcorderProfile.QUALITY_1080P -> 1920 * 1080
|
|
||||||
CamcorderProfile.QUALITY_2K -> 2048 * 1080
|
|
||||||
CamcorderProfile.QUALITY_QHD -> 2560 * 1440
|
|
||||||
CamcorderProfile.QUALITY_2160P -> 3840 * 2160
|
|
||||||
CamcorderProfile.QUALITY_4KDCI -> 4096 * 2160
|
|
||||||
CamcorderProfile.QUALITY_8KUHD -> 7680 * 4320
|
|
||||||
else -> throw Error("Invalid CamcorderProfile \"$camcorderProfile\"!")
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun findClosestCamcorderProfileQuality(cameraId: String, resolution: Size): Int {
|
|
||||||
// Iterate through all available CamcorderProfiles and find the one that matches the closest
|
|
||||||
val targetResolution = resolution.width * resolution.height
|
|
||||||
val cameraIdInt = cameraId.toIntOrNull()
|
|
||||||
|
|
||||||
val profiles = (CamcorderProfile.QUALITY_QCIF..CamcorderProfile.QUALITY_8KUHD).filter { profile ->
|
|
||||||
if (cameraIdInt != null) {
|
|
||||||
return@filter CamcorderProfile.hasProfile(cameraIdInt, profile)
|
|
||||||
} else {
|
|
||||||
return@filter CamcorderProfile.hasProfile(profile)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val closestProfile = profiles.minBy { profile ->
|
|
||||||
val currentResolution = getResolutionForCamcorderProfileQuality(profile)
|
|
||||||
return@minBy abs(currentResolution - targetResolution)
|
|
||||||
}
|
|
||||||
return closestProfile
|
|
||||||
}
|
|
||||||
|
@ -0,0 +1,101 @@
|
|||||||
|
package com.mrousavy.camera.utils
|
||||||
|
|
||||||
|
import android.media.CamcorderProfile
|
||||||
|
import android.os.Build
|
||||||
|
import android.util.Size
|
||||||
|
import kotlin.math.abs
|
||||||
|
|
||||||
|
class CamcorderProfileUtils {
|
||||||
|
companion object {
|
||||||
|
private fun getResolutionForCamcorderProfileQuality(camcorderProfile: Int): Int =
|
||||||
|
when (camcorderProfile) {
|
||||||
|
CamcorderProfile.QUALITY_QCIF -> 176 * 144
|
||||||
|
CamcorderProfile.QUALITY_QVGA -> 320 * 240
|
||||||
|
CamcorderProfile.QUALITY_CIF -> 352 * 288
|
||||||
|
CamcorderProfile.QUALITY_VGA -> 640 * 480
|
||||||
|
CamcorderProfile.QUALITY_480P -> 720 * 480
|
||||||
|
CamcorderProfile.QUALITY_720P -> 1280 * 720
|
||||||
|
CamcorderProfile.QUALITY_1080P -> 1920 * 1080
|
||||||
|
CamcorderProfile.QUALITY_2K -> 2048 * 1080
|
||||||
|
CamcorderProfile.QUALITY_QHD -> 2560 * 1440
|
||||||
|
CamcorderProfile.QUALITY_2160P -> 3840 * 2160
|
||||||
|
CamcorderProfile.QUALITY_4KDCI -> 4096 * 2160
|
||||||
|
CamcorderProfile.QUALITY_8KUHD -> 7680 * 4320
|
||||||
|
else -> throw Error("Invalid CamcorderProfile \"$camcorderProfile\"!")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun findClosestCamcorderProfileQuality(cameraId: String, resolution: Size, allowLargerSize: Boolean): Int {
|
||||||
|
// Iterate through all available CamcorderProfiles and find the one that matches the closest
|
||||||
|
val targetResolution = resolution.width * resolution.height
|
||||||
|
val cameraIdInt = cameraId.toIntOrNull()
|
||||||
|
|
||||||
|
var profiles = (CamcorderProfile.QUALITY_QCIF..CamcorderProfile.QUALITY_8KUHD).filter { profile ->
|
||||||
|
if (cameraIdInt != null) {
|
||||||
|
return@filter CamcorderProfile.hasProfile(cameraIdInt, profile)
|
||||||
|
} else {
|
||||||
|
return@filter CamcorderProfile.hasProfile(profile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!allowLargerSize) {
|
||||||
|
profiles = profiles.filter { profile ->
|
||||||
|
val currentResolution = getResolutionForCamcorderProfileQuality(profile)
|
||||||
|
return@filter currentResolution <= targetResolution
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val closestProfile = profiles.minBy { profile ->
|
||||||
|
val currentResolution = getResolutionForCamcorderProfileQuality(profile)
|
||||||
|
return@minBy abs(currentResolution - targetResolution)
|
||||||
|
}
|
||||||
|
return closestProfile
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getMaximumVideoSize(cameraId: String): Size? {
|
||||||
|
try {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
val profiles = CamcorderProfile.getAll(cameraId, CamcorderProfile.QUALITY_HIGH)
|
||||||
|
if (profiles != null) {
|
||||||
|
val largestProfile = profiles.videoProfiles.filterNotNull().maxByOrNull { it.width * it.height }
|
||||||
|
if (largestProfile != null) {
|
||||||
|
return Size(largestProfile.width, largestProfile.height)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val cameraIdInt = cameraId.toIntOrNull()
|
||||||
|
if (cameraIdInt != null) {
|
||||||
|
val profile = CamcorderProfile.get(cameraIdInt, CamcorderProfile.QUALITY_HIGH)
|
||||||
|
return Size(profile.videoFrameWidth, profile.videoFrameHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
// some Samsung phones just crash when trying to get the CamcorderProfile. Only god knows why.
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getMaximumFps(cameraId: String, size: Size): Int? {
|
||||||
|
try {
|
||||||
|
val quality = findClosestCamcorderProfileQuality(cameraId, size, false)
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
val profiles = CamcorderProfile.getAll(cameraId, quality)
|
||||||
|
if (profiles != null) {
|
||||||
|
return profiles.videoProfiles.maxOf { profile -> profile.frameRate }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val cameraIdInt = cameraId.toIntOrNull()
|
||||||
|
if (cameraIdInt != null) {
|
||||||
|
val profile = CamcorderProfile.get(cameraIdInt, quality)
|
||||||
|
return profile.videoFrameRate
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
// some Samsung phones just crash when trying to get the CamcorderProfile. Only god knows why.
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user