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 val aiInferenceLock = Any()
private var imageSegmenter: ImageSegmenter? = null
private var isAiSupported = true
private fun getImageSegmenter(): ImageSegmenter {
private fun getImageSegmenter(): ImageSegmenter? {
synchronized(aiInferenceLock) {
if (!isAiSupported) return null
if (imageSegmenter == null) {
val baseOptions = BaseOptions.builder()
.setModelAssetPath("models/multiclass_segmenter.tflite")
.build()
try {
val baseOptions = BaseOptions.builder()
.setModelAssetPath("models/multiclass_segmenter.tflite")
.build()
val options = ImageSegmenterOptions.builder()
.setBaseOptions(baseOptions)
.setOutputCategoryMask(true)
.setOutputConfidenceMasks(false)
.build()
val options = ImageSegmenterOptions.builder()
.setBaseOptions(baseOptions)
.setOutputCategoryMask(true)
.setOutputConfidenceMasks(false)
.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 fun getTfliteInterpreter(): org.tensorflow.lite.Interpreter {
private fun getTfliteInterpreter(): org.tensorflow.lite.Interpreter? {
synchronized(aiInferenceLock) {
if (!isAiSupported) return null
if (tfliteInterpreter == null) {
val assetFileDescriptor = assets.openFd("models/midas.tflite")
val inputStream = java.io.FileInputStream(assetFileDescriptor.fileDescriptor)
val fileChannel = inputStream.channel
val startOffset = assetFileDescriptor.startOffset
val declaredLength = assetFileDescriptor.declaredLength
val modelBuffer = fileChannel.map(java.nio.channels.FileChannel.MapMode.READ_ONLY, startOffset, declaredLength)
try {
val assetFileDescriptor = assets.openFd("models/midas.tflite")
val inputStream = java.io.FileInputStream(assetFileDescriptor.fileDescriptor)
val fileChannel = inputStream.channel
val startOffset = assetFileDescriptor.startOffset
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)
tfliteInterpreter = org.tensorflow.lite.Interpreter(modelBuffer, options)
val options = org.tensorflow.lite.Interpreter.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 {
val interpreter = getTfliteInterpreter()
val interpreter = getTfliteInterpreter() ?: return FloatArray(256 * 256)
// Input: 256x256 RGB
val scaled = android.graphics.Bitmap.createScaledBitmap(bitmap, 256, 256, true)
@@ -1713,11 +1744,29 @@ class MainActivity : AppCompatActivity() {
return capturedImage
}
if (!isAiSupported) {
if (blurIntensity > 0f) {
val radius = (25 * blurIntensity).toInt().coerceIn(1, 25)
return blurBitmap(capturedImage, radius)
}
return capturedImage
}
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
val mpImage = com.google.mediapipe.framework.image.BitmapImageBuilder(capturedImage).build()
val segmentationResult = synchronized(aiInferenceLock) {
getImageSegmenter().segment(mpImage)
segmenter.segment(mpImage)
}
val categoryMaskOptional = segmentationResult.categoryMask()
if (!categoryMaskOptional.isPresent) {