Fix emulator crash by adding runtime JNI loading check and fallback for MediaPipe Tasks Vision

This commit is contained in:
2026-07-06 17:53:04 +07:00
parent 88fc095bdc
commit 4035aefda4
@@ -67,48 +67,79 @@ class MainActivity : AppCompatActivity() {
private lateinit var backgroundOptions: List<BackgroundOption> private lateinit var backgroundOptions: List<BackgroundOption>
private val aiInferenceLock = Any() private val aiInferenceLock = Any()
private var imageSegmenter: ImageSegmenter? = null private var imageSegmenter: ImageSegmenter? = null
private var isAiSupported = true
private fun getImageSegmenter(): ImageSegmenter { private fun getImageSegmenter(): ImageSegmenter? {
synchronized(aiInferenceLock) { synchronized(aiInferenceLock) {
if (!isAiSupported) return null
if (imageSegmenter == null) { if (imageSegmenter == null) {
val baseOptions = BaseOptions.builder() try {
.setModelAssetPath("models/multiclass_segmenter.tflite") val baseOptions = BaseOptions.builder()
.build() .setModelAssetPath("models/multiclass_segmenter.tflite")
.build()
val options = ImageSegmenterOptions.builder() val options = ImageSegmenterOptions.builder()
.setBaseOptions(baseOptions) .setBaseOptions(baseOptions)
.setOutputCategoryMask(true) .setOutputCategoryMask(true)
.setOutputConfidenceMasks(false) .setOutputConfidenceMasks(false)
.build() .build()
imageSegmenter = ImageSegmenter.createFromOptions(this, options) imageSegmenter = ImageSegmenter.createFromOptions(this, options)
} catch (e: UnsatisfiedLinkError) {
isAiSupported = false
runOnUiThread {
Toast.makeText(this, "Không hỗ trợ xử lý AI trên thiết bị/emulator này (thiếu thư viện Native JNI)", Toast.LENGTH_LONG).show()
}
return null
} catch (e: Exception) {
isAiSupported = false
runOnUiThread {
Toast.makeText(this, "Lỗi khởi tạo AI Segmenter: ${e.message}", Toast.LENGTH_LONG).show()
}
return null
}
} }
return imageSegmenter!! return imageSegmenter
} }
} }
private var tfliteInterpreter: org.tensorflow.lite.Interpreter? = null private var tfliteInterpreter: org.tensorflow.lite.Interpreter? = null
private fun getTfliteInterpreter(): org.tensorflow.lite.Interpreter { private fun getTfliteInterpreter(): org.tensorflow.lite.Interpreter? {
synchronized(aiInferenceLock) { synchronized(aiInferenceLock) {
if (!isAiSupported) return null
if (tfliteInterpreter == null) { if (tfliteInterpreter == null) {
val assetFileDescriptor = assets.openFd("models/midas.tflite") try {
val inputStream = java.io.FileInputStream(assetFileDescriptor.fileDescriptor) val assetFileDescriptor = assets.openFd("models/midas.tflite")
val fileChannel = inputStream.channel val inputStream = java.io.FileInputStream(assetFileDescriptor.fileDescriptor)
val startOffset = assetFileDescriptor.startOffset val fileChannel = inputStream.channel
val declaredLength = assetFileDescriptor.declaredLength val startOffset = assetFileDescriptor.startOffset
val modelBuffer = fileChannel.map(java.nio.channels.FileChannel.MapMode.READ_ONLY, startOffset, declaredLength) val declaredLength = assetFileDescriptor.declaredLength
val modelBuffer = fileChannel.map(java.nio.channels.FileChannel.MapMode.READ_ONLY, startOffset, declaredLength)
val options = org.tensorflow.lite.Interpreter.Options()
options.setNumThreads(4) val options = org.tensorflow.lite.Interpreter.Options()
tfliteInterpreter = org.tensorflow.lite.Interpreter(modelBuffer, options) options.setNumThreads(4)
tfliteInterpreter = org.tensorflow.lite.Interpreter(modelBuffer, options)
} catch (e: UnsatisfiedLinkError) {
isAiSupported = false
runOnUiThread {
Toast.makeText(this, "Không hỗ trợ xử lý Depth trên thiết bị/emulator này (thiếu thư viện Native JNI)", Toast.LENGTH_LONG).show()
}
return null
} catch (e: Exception) {
isAiSupported = false
runOnUiThread {
Toast.makeText(this, "Lỗi khởi tạo Depth Interpreter: ${e.message}", Toast.LENGTH_LONG).show()
}
return null
}
} }
return tfliteInterpreter!! return tfliteInterpreter
} }
} }
private fun runDepthInference(bitmap: android.graphics.Bitmap): FloatArray { private fun runDepthInference(bitmap: android.graphics.Bitmap): FloatArray {
val interpreter = getTfliteInterpreter() val interpreter = getTfliteInterpreter() ?: return FloatArray(256 * 256)
// Input: 256x256 RGB // Input: 256x256 RGB
val scaled = android.graphics.Bitmap.createScaledBitmap(bitmap, 256, 256, true) val scaled = android.graphics.Bitmap.createScaledBitmap(bitmap, 256, 256, true)
@@ -1712,12 +1743,30 @@ class MainActivity : AppCompatActivity() {
if (!isBgActive) { if (!isBgActive) {
return capturedImage return capturedImage
} }
if (!isAiSupported) {
if (blurIntensity > 0f) {
val radius = (25 * blurIntensity).toInt().coerceIn(1, 25)
return blurBitmap(capturedImage, radius)
}
return capturedImage
}
try { try {
val segmenter = getImageSegmenter()
val interpreter = getTfliteInterpreter()
if (segmenter == null || interpreter == null) {
if (blurIntensity > 0f) {
val radius = (25 * blurIntensity).toInt().coerceIn(1, 25)
return blurBitmap(capturedImage, radius)
}
return capturedImage
}
// 1. Run MediaPipe Multiclass Image Segmentation to locate subject // 1. Run MediaPipe Multiclass Image Segmentation to locate subject
val mpImage = com.google.mediapipe.framework.image.BitmapImageBuilder(capturedImage).build() val mpImage = com.google.mediapipe.framework.image.BitmapImageBuilder(capturedImage).build()
val segmentationResult = synchronized(aiInferenceLock) { val segmentationResult = synchronized(aiInferenceLock) {
getImageSegmenter().segment(mpImage) segmenter.segment(mpImage)
} }
val categoryMaskOptional = segmentationResult.categoryMask() val categoryMaskOptional = segmentationResult.categoryMask()
if (!categoryMaskOptional.isPresent) { if (!categoryMaskOptional.isPresent) {