fix: Fix VideoPipeline crash on Samsung (Disable USAGE_GPU_SAMPLED_IMAGE
ImageReader) (#2555)
* fix: Fix VideoPipeline crash on Samsung (`USAGE_GPU_SAMPLED_IMAGE` not supported) * Format code
This commit is contained in:
parent
0130085376
commit
ad33dd91b1
@ -9,7 +9,6 @@ import android.os.Build
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.Surface
|
import android.view.Surface
|
||||||
import androidx.annotation.Keep
|
import androidx.annotation.Keep
|
||||||
import androidx.annotation.RequiresApi
|
|
||||||
import com.facebook.jni.HybridData
|
import com.facebook.jni.HybridData
|
||||||
import com.facebook.proguard.annotations.DoNotStrip
|
import com.facebook.proguard.annotations.DoNotStrip
|
||||||
import com.mrousavy.camera.frameprocessor.Frame
|
import com.mrousavy.camera.frameprocessor.Frame
|
||||||
@ -79,15 +78,12 @@ class VideoPipeline(
|
|||||||
val format = getImageReaderFormat()
|
val format = getImageReaderFormat()
|
||||||
Log.i(TAG, "Using ImageReader round-trip (format: #$format)")
|
Log.i(TAG, "Using ImageReader round-trip (format: #$format)")
|
||||||
|
|
||||||
|
imageReader = ImageReader.newInstance(width, height, format, MAX_IMAGES)
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
Log.i(TAG, "Using API 29 for GPU ImageReader...")
|
Log.i(TAG, "Using ImageWriter with custom format (#$format)...")
|
||||||
val usageFlags = getRecommendedHardwareBufferFlags()
|
|
||||||
Log.i(TAG, "Using ImageReader flags: $usageFlags")
|
|
||||||
imageReader = ImageReader.newInstance(width, height, format, MAX_IMAGES, usageFlags)
|
|
||||||
imageWriter = ImageWriter.newInstance(glSurface, MAX_IMAGES, format)
|
imageWriter = ImageWriter.newInstance(glSurface, MAX_IMAGES, format)
|
||||||
} else {
|
} else {
|
||||||
Log.i(TAG, "Using legacy API for CPU ImageReader...")
|
Log.i(TAG, "Using ImageWriter with default format...")
|
||||||
imageReader = ImageReader.newInstance(width, height, format, MAX_IMAGES)
|
|
||||||
imageWriter = ImageWriter.newInstance(glSurface, MAX_IMAGES)
|
imageWriter = ImageWriter.newInstance(glSurface, MAX_IMAGES)
|
||||||
}
|
}
|
||||||
imageReader!!.setOnImageAvailableListener({ reader ->
|
imageReader!!.setOnImageAvailableListener({ reader ->
|
||||||
@ -185,56 +181,6 @@ class VideoPipeline(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the recommended HardwareBuffer flags for creating ImageReader instances with.
|
|
||||||
*
|
|
||||||
* Tries to use [HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE] if possible, [HardwareBuffer.USAGE_CPU_READ_OFTEN]
|
|
||||||
* or a combination of both flags if CPU access is needed ([enableFrameProcessor]), and [0] otherwise.
|
|
||||||
*/
|
|
||||||
@RequiresApi(Build.VERSION_CODES.Q)
|
|
||||||
@Suppress("LiftReturnOrAssignment")
|
|
||||||
private fun getRecommendedHardwareBufferFlags(): Long {
|
|
||||||
val cpuFlag = HardwareBuffer.USAGE_CPU_READ_OFTEN
|
|
||||||
val gpuFlag = HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE
|
|
||||||
val bothFlags = gpuFlag or cpuFlag
|
|
||||||
|
|
||||||
if (format == PixelFormat.NATIVE) {
|
|
||||||
// We don't need CPU access, so we can use GPU optimized buffers
|
|
||||||
if (supportsHardwareBufferFlags(gpuFlag)) {
|
|
||||||
// We support GPU Buffers directly and
|
|
||||||
Log.i(TAG, "GPU HardwareBuffers are supported!")
|
|
||||||
return gpuFlag
|
|
||||||
} else {
|
|
||||||
// no flags are supported - fall back to default
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// We are using YUV or RGB formats, so we need CPU access on the Frame
|
|
||||||
if (supportsHardwareBufferFlags(bothFlags)) {
|
|
||||||
// We support both CPU and GPU flags!
|
|
||||||
Log.i(TAG, "GPU + CPU HardwareBuffers are supported!")
|
|
||||||
return bothFlags
|
|
||||||
} else if (supportsHardwareBufferFlags(cpuFlag)) {
|
|
||||||
// We only support a CPU read flag, that's fine
|
|
||||||
Log.i(TAG, "CPU HardwareBuffers are supported!")
|
|
||||||
return cpuFlag
|
|
||||||
} else {
|
|
||||||
// no flags are supported - fall back to default
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.Q)
|
|
||||||
private fun supportsHardwareBufferFlags(flags: Long): Boolean {
|
|
||||||
val hardwareBufferFormat = format.toHardwareBufferFormat()
|
|
||||||
try {
|
|
||||||
return HardwareBuffer.isSupported(width, height, hardwareBufferFormat, 1, flags)
|
|
||||||
} catch (_: Throwable) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private external fun getInputTextureId(): Int
|
private external fun getInputTextureId(): Int
|
||||||
private external fun onBeforeFrame()
|
private external fun onBeforeFrame()
|
||||||
private external fun onFrame(transformMatrix: FloatArray)
|
private external fun onFrame(transformMatrix: FloatArray)
|
||||||
|
@ -4,7 +4,6 @@ import android.graphics.ImageFormat
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.mrousavy.camera.core.InvalidTypeScriptUnionError
|
import com.mrousavy.camera.core.InvalidTypeScriptUnionError
|
||||||
import com.mrousavy.camera.core.PixelFormatNotSupportedError
|
import com.mrousavy.camera.core.PixelFormatNotSupportedError
|
||||||
import com.mrousavy.camera.utils.HardwareBufferUtils
|
|
||||||
import com.mrousavy.camera.utils.ImageFormatUtils
|
import com.mrousavy.camera.utils.ImageFormatUtils
|
||||||
|
|
||||||
enum class PixelFormat(override val unionValue: String) : JSUnionValue {
|
enum class PixelFormat(override val unionValue: String) : JSUnionValue {
|
||||||
@ -20,11 +19,6 @@ enum class PixelFormat(override val unionValue: String) : JSUnionValue {
|
|||||||
else -> throw PixelFormatNotSupportedError(this.unionValue)
|
else -> throw PixelFormatNotSupportedError(this.unionValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toHardwareBufferFormat(): Int {
|
|
||||||
val imageFormat = toImageFormat()
|
|
||||||
return HardwareBufferUtils.getHardwareBufferFormat(imageFormat)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object : JSUnionValue.Companion<PixelFormat> {
|
companion object : JSUnionValue.Companion<PixelFormat> {
|
||||||
private const val TAG = "PixelFormat"
|
private const val TAG = "PixelFormat"
|
||||||
fun fromImageFormat(imageFormat: Int): PixelFormat =
|
fun fromImageFormat(imageFormat: Int): PixelFormat =
|
||||||
|
@ -1,36 +0,0 @@
|
|||||||
package com.mrousavy.camera.utils
|
|
||||||
|
|
||||||
import android.graphics.ImageFormat
|
|
||||||
import android.hardware.HardwareBuffer
|
|
||||||
import android.media.ImageReader
|
|
||||||
import android.os.Build
|
|
||||||
|
|
||||||
class HardwareBufferUtils {
|
|
||||||
companion object {
|
|
||||||
fun getHardwareBufferFormat(imageFormat: Int): Int {
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
||||||
// Dynamically create an ImageReader with the target ImageFormat, and then
|
|
||||||
// get it's HardwareBuffer format to see what it uses underneath.
|
|
||||||
val imageReader = ImageReader.newInstance(1, 1, imageFormat, 1, HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE)
|
|
||||||
val format = imageReader.hardwareBufferFormat
|
|
||||||
imageReader.close()
|
|
||||||
return format
|
|
||||||
}
|
|
||||||
|
|
||||||
if (imageFormat == ImageFormat.PRIVATE) {
|
|
||||||
// PRIVATE formats are opaque, their actual equivalent HardwareBuffer format is unknown.
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
||||||
// We can assume that YUV 4:2:0 or RGB is used.
|
|
||||||
return HardwareBuffer.YCBCR_420_888
|
|
||||||
} else {
|
|
||||||
// Maybe assume we are on RGB if we're not on API R or above...
|
|
||||||
return HardwareBuffer.RGB_888
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// According to PublicFormat.cpp in Android's codebase, the formats map 1:1 anyways..
|
|
||||||
// https://cs.android.com/android/platform/superproject/main/+/main:frameworks/native/libs/ui/PublicFormat.cpp
|
|
||||||
return imageFormat
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user