Отслеживание ARCore случайно зависает в режиме ПАУЗЫ при запуске приложения, восстанавливается после фонового/переднего Android

Форум для тех, кто программирует под Android
Ответить
Anonymous
 Отслеживание ARCore случайно зависает в режиме ПАУЗЫ при запуске приложения, восстанавливается после фонового/переднего

Сообщение Anonymous »

Я разрабатываю простое приложение AR-линейки для Android на основе Google ARCore.
Приложение концептуально очень простое и следует стандартному конвейеру ARCore:
  • Рендеринг фона камеры
  • Визуализация характерных точек
  • Обнаружение плоскости
  • Нажмите, чтобы разместить две точки и измерить расстояние
    Однако я столкнулся с серьезной проблемой трудно диагностируемая нестабильность отслеживания, которую мне не удалось устранить, несмотря на несколько дней отладки.
Ожидаемое поведение
При запуске приложения:
Инициализация сеанса ARCore
Отслеживание переходов из режима ПАУЗА → ОТСЛЕЖИВАНИЕ
Появляются функциональные точки
Плоскости обнаруживаются последовательно
Фактическое поведение (проблема)
Поведение случайное и недетерминированное:
  • При холодном запуске приложение часто зависает в
    TrackingState.PAUSED на неопределенный срок
  • Самолеты не обнаружены
  • Функциональные точки минимальны или отсутствуют
  • Иногда приложение отправляется в фоновый режим, а затем разблокируется на переднем плане отслеживание
  • Отслеживание переходов в режим ОТСЛЕЖИВАНИЕ
  • Появляются характерные точки и плоскости
  • Даже когда оно разблокируется, отслеживание нестабильно:
  • Быстрое мигание между ОТСЛЕЖИВАНИЕМ и ПАУЗОЙ
  • Эта нестабильность препятствует надежному обнаружению плоскости
  • Измерения AR становятся непригодными
  • Журналы отображаются быстро чередование:
    ОТСЛЕЖИВАНИЕ → ПАУЗА → ОТСЛЕЖИВАНИЕ → ПАУЗА
То, что я уже проверил/исключил. Я тщательно проверил и протестировал следующие моменты:
Жизненный цикл ARCore
  • Session.configure() вызывается до session.resume()
  • session.resume() и session.pause() в потоке пользовательского интерфейса
  • session.update() вызывается из потока GL/render
  • Нет одновременных вызовов резюме() / пауза()
✅ Текстура камеры и настройка GL.
  • Текстура камеры (setCameraTextureName) устанавливается один раз, после создания контекста GL.
  • Нет повторных вызовов для каждого кадра.
    - Больше нет проблем с черным экраном.
✅ Режим обновления.
  • Проверено. оба:
  • Config.UpdateMode.BLOCKING
  • Config.UpdateMode.LATEST_CAMERA_IMAGE
  • Нет изменений в поведении
✅ Проверка попадания и логика рендеринга
  • Проверки попадания отключены, пока отслеживание ПРИОСТАНОВЛЕНО
  • Логика рендеринга не требует принудительного обновления во время ПАУЗЫ
  • Мерцание не вызвано отрисовкой условий
✅ Устройство и среда
  • Похожие приложения (например, AR Ruler) работают хорошо
  • Происходит при нескольких запусках на одном устройстве
  • Одинаковое освещение и среда
  • ARCore установлен и запущен на сегодняшний день
  • Разрешения камеры предоставлены правильно
Вывод
На данный момент удобство использования приложения скомпрометировано случайным отслеживанием сбоев и нестабильности запуска, и у меня заканчиваются гипотезы.
Любая информация от разработчиков с глубоким опытом работы с ARCore будет чрезвычайно признательна.
ЗДЕСЬ мой КОД (в этой версии) Я только инициализировал AR)
класс MainActivity : AppCompatActivity(), GLSurfaceView.Renderer {

Код: Выделить всё

companion object {
private const val TAG = "MainActivity"
}

// UI
private lateinit var surfaceView: GLSurfaceView
private lateinit var trackingStateText: TextView

// ARCore
private var session: Session? = null
private var installRequested = false
private var isGLInitCompleted = false
private var lastTrackingState: TrackingState? = null

// Helpers
private lateinit var displayRotationHelper: DisplayRotationHelper

// Renderers
private lateinit var backgroundRenderer: BackgroundRenderer
private lateinit var pointCloudRenderer: PointCloudRenderer

// MVP matrices
private val projectionMatrix = FloatArray(16)
private val viewMatrix = FloatArray(16)
private val viewProjectionMatrix = FloatArray(16)

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

// UI
surfaceView = findViewById(R.id.surfaceView)
trackingStateText = findViewById(R.id.trackingStateText)

// Display rotation helper
displayRotationHelper = DisplayRotationHelper(this)

// GLSurfaceView setup
//surfaceView.holder.setFixedSize(480, 960) // RENDERING SCALING: 540x960 -> 1080x2185 (HW Scaler)
surfaceView.preserveEGLContextOnPause = true
surfaceView.setEGLContextClientVersion(2)
surfaceView.setEGLConfigChooser(8, 8, 8, 8, 16, 0)
surfaceView.setRenderer(this)
surfaceView.renderMode = GLSurfaceView.RENDERMODE_CONTINUOUSLY

// Renderers (init dopo, su GL thread)
backgroundRenderer = BackgroundRenderer()
pointCloudRenderer = PointCloudRenderer()

Log.d(TAG, "onCreate completed")
}

override fun onResume() {
super.onResume()

// Check camera permission
if (!CameraPermissionHelper.hasCameraPermission(this)) {
CameraPermissionHelper.requestCameraPermission(this)
return
}

// Check ARCore availability
try {
when (ArCoreApk.getInstance().requestInstall(this, !installRequested)) {
ArCoreApk.InstallStatus.INSTALL_REQUESTED -> {
installRequested = true
return
}
ArCoreApk.InstallStatus.INSTALLED -> {
// Continue
}
else ->  {
// Handle other cases
}
}
} catch (e: UnavailableException) {
Log.e(TAG, "ARCore not available", e)
Toast.makeText(this, "ARCore not available: ${e.message}", Toast.LENGTH_LONG).show()
finish()
return
}

// Create session se necessario
if (session == null) {
try {
session = Session(this)
configureSession()
Log.d(TAG, "Session created and configured")
val size = session!!.cameraConfig.imageSize
Log.e(TAG, "VERSIONE 6")
Log.e(TAG, "ACTUAL RUNNING CONFIG: ${size.width}x${size.height}")
} catch (e: Exception) {
Log.e(TAG, "Failed to create session", e)
Toast.makeText(this, "Failed to create ARCore session", Toast.LENGTH_LONG).show()
finish()
return
}
}

// CRITICO: Resume session SOLO se GL è inizializzato (texture già settata)
// Al primo avvio: NON fare resume qui.  Session sarà resumata in onSurfaceCreated
//                  DOPO setCameraTextureName (ordine: texture → resume → update)
// Nei resume successivi: GL pronto, texture valida, resume qui
if (isGLInitCompleted) {
try {
session?.setCameraTextureName(backgroundRenderer.getTextureId())
session?.resume()
Log.d(TAG, "Session resumed from onResume (GL ready)")
} catch (e: CameraNotAvailableException) {
Log.e(TAG, "Camera not available", e)
Toast.makeText(this, "Camera not available", Toast.LENGTH_LONG).show()
session = null
finish()
return
}
} else {
Log.d(TAG, "Session created but NOT resumed (waiting for GL init)")
}

// Resume helpers
displayRotationHelper.onResume()
surfaceView.onResume()
surfaceView.visibility = View.VISIBLE
}

override fun onPause() {
super.onPause()

// Pause SOLO se init completato
if (isGLInitCompleted) {
displayRotationHelper.onPause()
surfaceView.visibility = View.GONE
surfaceView.onPause()
session?.pause()
Log.d(TAG, "Session paused")
}
}

override fun onDestroy() {
super.onDestroy()

if (isGLInitCompleted) {
session?.close()
session = null
Log.d(TAG, "Session closed")
}
}

override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)

if (!CameraPermissionHelper.hasCameraPermission(this)) {
Toast.makeText(this, "Camera permission required", Toast.LENGTH_LONG).show()
if (!CameraPermissionHelper.shouldShowRequestPermissionRationale(this)) {
CameraPermissionHelper.launchPermissionSettings(this)
}
finish()
}
}

/**
* Configura ARCore Session.
* Li aggiungiamo come TENTATIVO per vedere se migliorano stabilità tracking.
*/
private fun configureSession() {
Log.e(TAG, "CHIAMO CONFIGURE SESSION")
val s = session ?: return

// CRITICO: Seleziona camera config a 307k pixel (640x480)
// setCameraConfig DEVE essere chiamato PRIMA di session.resume()
selectBestCameraConfig(s)

val config = Config(s).apply {
focusMode = Config.FocusMode.AUTO
// PRIMA: updateMode = Config.UpdateMode.BLOCKING
// DOPO:
updateMode = Config.UpdateMode.LATEST_CAMERA_IMAGE

// PRIMA: planeFindingMode = Config.PlaneFindingMode.DISABLED
// DOPO:
planeFindingMode = Config.PlaneFindingMode.HORIZONTAL_AND_VERTICAL

// DepthMode: DISABLED (default).
// AUTOMATIC aggiunge carico computazionale inutile per questa fase.
}

s.configure(config)
}

/**
* Seleziona la camera config con risoluzione piu vicina a 307.200 pixel (640x480).
* Riduce il carico di processing → tracking piu stabile.
*/
private fun selectBestCameraConfig(session: Session) {
try {
val filter = CameraConfigFilter(session)
filter.setTargetFps(EnumSet.of(CameraConfig.TargetFps.TARGET_FPS_30))

val configs = session.getSupportedCameraConfigs(filter)
if (configs.isEmpty()) {
Log.w(TAG, "No supported camera configs found, using default")
return
}

// Trova la config piu vicina a 307.200 pixel (640x480)
val TARGET_PIXELS = 307200.0f
var bestConfig: CameraConfig? = null
var bestDiff = Float.MAX_VALUE

for (config in configs) {
val size = config.imageSize
val pixels = (size.width * size.height).toFloat()
val diff = Math.abs(TARGET_PIXELS - pixels)
if (diff < bestDiff) {
bestConfig = config
bestDiff = diff
}
}

if (bestConfig != null) {
session.cameraConfig = bestConfig
val size = bestConfig.imageSize
Log.d(TAG, "Camera config: ${size.width}x${size.height} "  +
"(${size.width * size.height} pixels, target=$TARGET_PIXELS)")
}
} catch (e: Exception) {
Log.e(TAG, "Failed to select camera config, using default", e)
}
}

// ========== GLSurfaceView.Renderer ==========

override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
GLES20.glClearColor(0.1f, 0.1f, 0.1f, 1.0f)

try {
// Init renderers su GL thread
backgroundRenderer.createOnGlThread(this)
pointCloudRenderer.createOnGlThread(this)

// 1. Set texture PRIMA di resume
session?.setCameraTextureName(backgroundRenderer.getTextureId())

isGLInitCompleted = true

// 2. Resume session ORA che la texture è settata
// Al primo avvio, onResume() NON ha fatto resume (perché isGLInitCompleted era false)
// Quindi la session è in stato "paused" e la resumiamo qui
session?.resume()

Log.d(TAG, "GL init complete, texture=${backgroundRenderer.getTextureId()}, session resumed")
} catch (e: CameraNotAvailableException) {
Log.e(TAG, "Camera not available on first resume", e)
finish()
} catch (e: Exception) {
Log.e(TAG, "Failed to initialize GL", e)
}
}

override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
GLES20.glViewport(0, 0, width, height)
displayRotationHelper.onSurfaceChanged(width, height)
Log.d(TAG, "Surface changed: ${width}x${height}")
}

override fun onDrawFrame(gl: GL10?) {
// Clear
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT or GLES20.GL_DEPTH_BUFFER_BIT)

val session = this.session ?: return

// Skip se GL non pronto
if (!isGLInitCompleted) {
return
}

try {
// CRITICO: setDisplayGeometry PRIMA di update()
displayRotationHelper.updateSessionIfNeeded(session)

// ===== UPDATE PHASE =====
val frame = session.update()
val camera = frame.camera

if (camera.trackingState == TrackingState.PAUSED) {
val reason = camera.trackingFailureReason
Log.e(TAG, "PAUSED reason: $reason")
}

// .use {} garantisce close() in tutti i path (normale + eccezione)
frame.acquirePointCloud().use { pointCloud ->
pointCloudRenderer.update(pointCloud)
}

// Update tracking state su UI thread
// FIX PERFORMANCE: Aggiorna la UI solo se lo stato cambia
if (camera.trackingState != lastTrackingState) {
lastTrackingState = camera.trackingState
updateTrackingStateUI(camera.trackingState, camera.trackingFailureReason)
}

// ===== DRAW PHASE =====

// 1. Draw camera background SEMPRE (anche se tracking PAUSED)
// 2. Se tracking PAUSED, skip overlay (ma background gia disegnato!)
if (camera.trackingState == TrackingState.PAUSED) {
return
}

// 3. Update MVP matrices
camera.getProjectionMatrix(projectionMatrix, 0, 0.1f, 100.0f)
camera.getViewMatrix(viewMatrix, 0)
android.opengl.Matrix.multiplyMM(
viewProjectionMatrix, 0,
projectionMatrix, 0,
viewMatrix, 0
)

// 4. Draw point cloud
pointCloudRenderer.draw(viewProjectionMatrix)

} catch (e: Exception) {
Log.e(TAG, "Error in onDrawFrame", e)
}
}

/**
* Aggiorna UI tracking state (chiamato da GL thread).
*/
private fun updateTrackingStateUI(state: TrackingState, reason: com.google.ar.core.TrackingFailureReason?) {
runOnUiThread {
val text = when (state) {
TrackingState.TRACKING -> "✓ TRACKING"
TrackingState.PAUSED -> "⚠ PAUSED - ${reason?.name ?: "unknown"}"
TrackingState.STOPPED -> "✗ STOPPED"
}

val color = when (state) {
TrackingState.TRACKING -> 0xFF00FF00.toInt()  // Green
TrackingState.PAUSED -> 0xFFFFAA00.toInt()    // Orange
TrackingState.STOPPED -> 0xFFFF0000.toInt()   // Red
}

trackingStateText.text = text
trackingStateText.setTextColor(color)
}
}
}

Подробнее здесь: https://stackoverflow.com/questions/798 ... background
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «Android»