Я пытаюсь сделать плагин для камеры, который я планирую использовать внутри Unity. Камера, кажется, запускается (также имея некоторые проблемы при установлении разрешения), но когда я помещаю приложение в задний план, камера должна останавливаться, и это происходит, но с этой ошибкой < /p>
2025-09-22 16:17:29.347 30471 30594 Error CameraCaptureSession Session 0: Exception while stopping repeating:
2025-09-22 16:17:29.347 30471 30594 Error CameraCaptureSession android.hardware.camera2.CameraAccessException: CAMERA_ERROR (3): The camera device has encountered a serious error
2025-09-22 16:17:29.347 30471 30594 Error CameraCaptureSession at android.hardware.camera2.impl.CameraDeviceImpl.checkIfCameraClosedOrInError(CameraDeviceImpl.java:2612)
2025-09-22 16:17:29.347 30471 30594 Error CameraCaptureSession at android.hardware.camera2.impl.CameraDeviceImpl.stopRepeating(CameraDeviceImpl.java:1469)
2025-09-22 16:17:29.347 30471 30594 Error CameraCaptureSession at android.hardware.camera2.impl.CameraCaptureSessionImpl.close(CameraCaptureSessionImpl.java:579)
2025-09-22 16:17:29.347 30471 30594 Error CameraCaptureSession at androidx.camera.camera2.internal.SynchronizedCaptureSessionBaseImpl.close(SynchronizedCaptureSessionBaseImpl.java:476)
2025-09-22 16:17:29.347 30471 30594 Error CameraCaptureSession at androidx.camera.camera2.internal.CaptureSession.release(CaptureSession.java:526)
2025-09-22 16:17:29.347 30471 30594 Error CameraCaptureSession at androidx.camera.camera2.internal.Camera2CameraImpl.releaseSession(Camera2CameraImpl.java:555)
2025-09-22 16:17:29.347 30471 30594 Error CameraCaptureSession at androidx.camera.camera2.internal.Camera2CameraImpl.resetCaptureSession(Camera2CameraImpl.java:1329)
2025-09-22 16:17:29.347 30471 30594 Error CameraCaptureSession at androidx.camera.camera2.internal.Camera2CameraImpl.closeCamera(Camera2CameraImpl.java:468)
2025-09-22 16:17:29.347 30471 30594 Error CameraCaptureSession at androidx.camera.camera2.internal.Camera2CameraImpl$StateCallback.handleErrorOnOpen(Camera2CameraImpl.java:1798)
2025-09-22 16:17:29.347 30471 30594 Error CameraCaptureSession at androidx.camera.camera2.internal.Camera2CameraImpl$StateCallback.onError(Camera2CameraImpl.java:1754)
2025-09-22 16:17:29.347 30471 30594 Error CameraCaptureSession at androidx.camera.camera2.internal.CameraDeviceStateCallbacks$ComboDeviceStateCallback.onError(CameraDeviceStateCallbacks.java:121)
2025-09-22 16:17:29.347 30471 30594 Error CameraCaptureSession at android.hardware.camera2.impl.CameraDeviceImpl$ClientStateCallback$4.run(CameraDeviceImpl.java:338)
2025-09-22 16:17:29.347 30471 30594 Error CameraCaptureSession at androidx.camera.core.impl.utils.executor.SequentialExecutor$1.run(SequentialExecutor.java:111)
2025-09-22 16:17:29.347 30471 30594 Error CameraCaptureSession at androidx.camera.core.impl.utils.executor.SequentialExecutor$QueueWorker.workOnQueue(SequentialExecutor.java:231)
2025-09-22 16:17:29.347 30471 30594 Error CameraCaptureSession at androidx.camera.core.impl.utils.executor.SequentialExecutor$QueueWorker.run(SequentialExecutor.java:173)
2025-09-22 16:17:29.347 30471 30594 Error CameraCaptureSession at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
2025-09-22 16:17:29.347 30471 30594 Error CameraCaptureSession at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:644)
> 2025-09-22 16:17:29.347 30471 30594 Error CameraCaptureSession at java.lang.Thread.run(Thread.java:1012)
< /code>
Вот мой код < /p>
package com.android.cameraplugin;
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.ImageFormat;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.util.Size;
import androidx.annotation.NonNull;
import androidx.camera.core.AspectRatio;
import androidx.camera.core.Camera;
import androidx.camera.core.CameraSelector;
import androidx.camera.core.ImageAnalysis;
import androidx.camera.core.ImageProxy;
import androidx.camera.lifecycle.ProcessCameraProvider;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LifecycleRegistry;
import com.google.common.util.concurrent.ListenableFuture;
import java.nio.ByteBuffer;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CameraPlugin {
private static final String TAG = "CameraPlugin";
private static CameraPlugin instance;
private final Activity activity;
private final Context context;
// CameraX components
private ProcessCameraProvider cameraProvider;
private Camera camera;
private ImageAnalysis imageAnalysis;
private CameraSelector cameraSelector;
// Threading
private final ExecutorService cameraExecutor;
private final Handler mainHandler = new Handler(Looper.getMainLooper());
// Our explicit lifecycle for CameraX
private final CustomLifecycle camLifecycle = new CustomLifecycle();
// Camera state
private boolean isCameraStarted = false;
private boolean isFrontCamera = false;
private int targetWidth = 1280;
private int targetHeight = 720;
// Rapid-cycling detection (kept from your original)
private long lastBackgroundTime = 0;
private int backgroundCount = 0;
private static final long BACKGROUND_DETECTION_WINDOW = 5000; // 5 seconds
private boolean isRapidCyclingMode = false;
private long rapidCyclingStartTime = 0;
private static final long RAPID_CYCLING_COOLDOWN = 10000; // 10 seconds
// RGB data storage
private volatile byte[] latestRGBData;
private volatile int frameWidth = 640;
private volatile int frameHeight = 480;
// Image analysis callback (YUV_420_888 guaranteed)
private final ImageAnalysis.Analyzer imageAnalyzer = new ImageAnalysis.Analyzer() {
private long lastProcessTime = 0;
private static final long MIN_PROCESS_INTERVAL = 50; // ~20 FPS max
private int frameSkipCounter = 0;
private static final int FRAME_SKIP_RATIO = 2; // Process every 2nd frame
@Override
public void analyze(@NonNull ImageProxy image) {
try {
// Log first few frames to debug
if (frameSkipCounter < 5) {
Log.d(TAG, "ImageAnalyzer received frame #" + frameSkipCounter +
" - format: " + image.getFormat() +
", size: " + image.getWidth() + "x" + image.getHeight());
}
// Throttle/skip for CPU budget
frameSkipCounter++;
if (frameSkipCounter % FRAME_SKIP_RATIO != 0) return;
long now = System.currentTimeMillis();
if (now - lastProcessTime < MIN_PROCESS_INTERVAL) return;
lastProcessTime = now;
if (image.getFormat() != ImageFormat.YUV_420_888) {
Log.w(TAG, "Unexpected format: " + image.getFormat() + " (expected YUV_420_888)");
}
byte[] rgb = yuv420888ToRgb(image);
if (rgb != null) {
latestRGBData = rgb;
frameWidth = image.getWidth();
frameHeight = image.getHeight();
Log.v(TAG, "Frame " + frameWidth + "x" + frameHeight + " -> RGB bytes=" + rgb.length);
}
} catch (Throwable t) {
Log.e(TAG, "analyze error: " + t.getMessage(), t);
} finally {
image.close();
}
}
};
private CameraPlugin(Activity activity) {
this.activity = activity;
this.context = activity.getApplicationContext();
this.cameraExecutor = Executors.newSingleThreadExecutor();
// Initialize CustomLifecycle on main thread
if (Looper.myLooper() == Looper.getMainLooper()) {
camLifecycle.initialize();
} else {
mainHandler.post(camLifecycle::initialize);
}
initializeCameraProvider();
}
public static CameraPlugin getInstance(Activity activity) {
if (instance == null) {
instance = new CameraPlugin(activity);
}
return instance;
}
private void initializeCameraProvider() {
ListenableFuture
cameraProviderFuture =
ProcessCameraProvider.getInstance(context);
cameraProviderFuture.addListener(() -> {
try {
cameraProvider = cameraProviderFuture.get();
Log.d(TAG, "CameraX provider initialized");
try {
var infos = cameraProvider.getAvailableCameraInfos();
Log.d(TAG, "Available cameras: " + infos.size());
for (int i = 0; i < infos.size(); i++) {
Log.d(TAG, "Camera " + i + ": " + infos.get(i));
}
} catch (Exception e) {
Log.w(TAG, "Could not list camera infos: " + e.getMessage());
}
} catch (ExecutionException | InterruptedException e) {
Log.e(TAG, "Failed to init CameraX provider: " + e.getMessage(), e);
}
}, ContextCompat.getMainExecutor(context));
}
public boolean checkCameraPermission() {
return ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA)
== PackageManager.PERMISSION_GRANTED;
}
public void requestCameraPermission() {
if (!checkCameraPermission()) {
ActivityCompat.requestPermissions(activity,
new String[]{Manifest.permission.CAMERA}, 100);
}
}
public boolean startCamera() {
return startCamera(targetWidth, targetHeight);
}
public boolean startCamera(int targetWidth, int targetHeight) {
Log.d(TAG, "Starting CameraX with " + targetWidth + "x" + targetHeight);
if (isRapidCyclingMode) {
Log.w(TAG, "Blocked: rapid cycling mode");
return false;
}
if (!checkCameraPermission()) {
Log.e(TAG, "Camera permission not granted");
return false;
}
if (cameraProvider == null) {
Log.e(TAG, "Camera provider not ready");
return false;
}
if (isCameraStarted) {
Log.d(TAG, "Camera already started");
return true;
}
if (Looper.myLooper() != Looper.getMainLooper()) {
mainHandler.post(() -> startCameraOnMainThread(targetWidth, targetHeight));
return true;
} else {
return startCameraOnMainThread(targetWidth, targetHeight);
}
}
private boolean startCameraOnMainThread(int targetWidth, int targetHeight) {
try {
this.targetWidth = targetWidth;
this.targetHeight = targetHeight;
Log.d(TAG, "Requesting camera resolution: " + targetWidth + "x" + targetHeight);
cameraSelector = new CameraSelector.Builder()
.requireLensFacing(isFrontCamera ? CameraSelector.LENS_FACING_FRONT
: CameraSelector.LENS_FACING_BACK)
.build();
// Force a standard resolution that's commonly supported
// Use 1280x720 (HD) which is almost universally supported
Size targetSize = new Size(1280, 720);
Log.d(TAG, "Forcing target size to: " + targetSize + " (ignoring requested " + targetWidth + "x" + targetHeight + ")");
imageAnalysis = new ImageAnalysis.Builder()
.setTargetResolution(targetSize)
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
// Force YUV_420_888 output to the analyzer
.setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_YUV_420_888)
.build();
imageAnalysis.setAnalyzer(cameraExecutor, imageAnalyzer);
cameraProvider.unbindAll();
try {
camera = cameraProvider.bindToLifecycle(
camLifecycle,
cameraSelector,
imageAnalysis
);
// Move custom lifecycle to RESUMED state to actually start the camera
mainHandler.post(camLifecycle::toResumed);
isCameraStarted = true;
Log.d(TAG, "CameraX started (YUV_420_888, analysis only)");
return true;
} catch (Exception bindingError) {
Log.e(TAG, "Failed to bind camera to lifecycle: " + bindingError.getMessage(), bindingError);
// Clean up on binding failure
if (cameraProvider != null) {
try {
cameraProvider.unbindAll();
} catch (Exception cleanupError) {
Log.e(TAG, "Cleanup error after binding failure: " + cleanupError.getMessage());
}
}
return false;
}
} catch (Exception e) {
Log.e(TAG, "Failed to start CameraX: " + e.getMessage(), e);
return false;
}
}
public void stopCamera() {
Log.d(TAG, "Stopping CameraX");
if (Looper.myLooper() != Looper.getMainLooper()) {
mainHandler.post(this::stopCameraOnMainThread);
} else {
stopCameraOnMainThread();
}
}
private void stopCameraOnMainThread() {
Log.d(TAG, "Stopping CameraX (sync)");
// 0) Prevent new analyze callbacks while tearing down
try {
if (imageAnalysis != null) imageAnalysis.clearAnalyzer();
} catch (Exception ignore) {}
// 1) Unbind all use cases NOW (not posted)
if (cameraProvider != null) {
try { cameraProvider.unbindAll(); } catch (Exception e) {
Log.e(TAG, "unbindAll error: " + e.getMessage(), e);
}
}
// 2) Drive lifecycle down NOW (not posted)
try { camLifecycle.toCreated(); } catch (Exception e) {
Log.e(TAG, "Error setting lifecycle to CREATED: " + e.getMessage());
}
// 3) Null state
isCameraStarted = false;
camera = null;
imageAnalysis = null;
Log.d(TAG, "CameraX stopped");
}
private void stopCameraImmediately() {
Log.d(TAG, "Immediate stop (break rapid cycling)");
if (cameraProvider != null) {
try {
Log.d(TAG, "Immediate stop - unbinding all use cases");
cameraProvider.unbindAll();
} catch (Exception e) {
Log.e(TAG, "Immediate unbindAll error: " + e.getMessage(), e);
}
}
// Move custom lifecycle to CREATED state immediately
try {
if (Looper.myLooper() == Looper.getMainLooper()) {
camLifecycle.toCreated();
} else {
mainHandler.post(camLifecycle::toCreated);
}
} catch (Exception e) {
Log.e(TAG, "Error setting lifecycle to CREATED (immediate): " + e.getMessage());
}
isCameraStarted = false;
camera = null;
imageAnalysis = null;
}
public void pauseCamera() { Log.d(TAG, "pauseCamera (lifecycle-driven)"); }
public void resumeCamera() { Log.d(TAG, "resumeCamera (lifecycle-driven)"); }
public void switchCamera() {
Log.d(TAG, "Switching camera");
if (isCameraStarted) {
stopCamera();
isFrontCamera = !isFrontCamera;
startCamera();
} else {
isFrontCamera = !isFrontCamera;
}
}
public boolean isUsingFrontCamera() { return isFrontCamera; }
public boolean isCameraRunning() { return isCameraStarted && camera != null; }
// Unity-facing frame accessors
public boolean hasNewFrame() { return latestRGBData != null; }
public byte[] getLatestFrameData() { return latestRGBData; }
public byte[] getLatestRGBFrame() { return latestRGBData; }
public int getFrameWidth() { return frameWidth; }
public int getFrameHeight() { return frameHeight; }
public void clearFrameData() { latestRGBData = null; }
public boolean isCameraInitialized() { return isCameraStarted; }
public boolean isCameraReady() { return isCameraStarted && camera != null && latestRGBData != null; }
public void setTargetResolution(int width, int height) {
this.targetWidth = width;
this.targetHeight = height;
}
public int getTargetWidth() { return targetWidth; }
public int getTargetHeight() { return targetHeight; }
public void setZoom(float zoomLevel) {
Log.d(TAG, "setZoom called with: " + zoomLevel);
// Implement via CameraControl if/when needed
}
public void switchCamera(String direction) {
Log.d(TAG, "switchCamera(direction=" + direction + ")");
switchCamera();
}
public void breakRapidCycling() {
Log.d(TAG, "breakRapidCycling called");
backgroundCount = 0;
lastBackgroundTime = 0;
isRapidCyclingMode = false;
rapidCyclingStartTime = 0;
if (isCameraStarted) stopCameraImmediately();
}
public boolean isInRapidCycling() { return isRapidCyclingMode; }
// Manual camera control methods
public void startCameraManually() {
Log.d(TAG, "Manually starting camera via custom lifecycle");
if (!isRapidCyclingMode) {
mainHandler.post(camLifecycle::toResumed);
}
}
public void stopCameraManually() {
Log.d(TAG, "Manually stopping camera via custom lifecycle");
mainHandler.post(camLifecycle::toStarted);
}
// Handle camera errors and recovery
public void handleCameraError() {
Log.e(TAG, "Handling camera error - stopping and resetting");
// Stop camera immediately
stopCameraImmediately();
// Reset rapid cycling mode
isRapidCyclingMode = false;
backgroundCount = 0;
lastBackgroundTime = 0;
rapidCyclingStartTime = 0;
// Wait a bit before allowing restart
mainHandler.postDelayed(() -> {
Log.d(TAG, "Camera error recovery - ready for restart");
}, 2000); // 2 second delay
}
// --------- YUV_420_888 -> RGB (interleaved) WITHOUT JPEG ----------
// Handles arbitrary row/pixel strides for Y, U, V planes.
private static byte[] yuv420888ToRgb(ImageProxy image) {
final int width = image.getWidth();
final int height = image.getHeight();
ImageProxy.PlaneProxy yPlane = image.getPlanes()[0];
ImageProxy.PlaneProxy uPlane = image.getPlanes()[1];
ImageProxy.PlaneProxy vPlane = image.getPlanes()[2];
ByteBuffer yBuf = yPlane.getBuffer();
ByteBuffer uBuf = uPlane.getBuffer();
ByteBuffer vBuf = vPlane.getBuffer();
int yRowStride = yPlane.getRowStride();
int yPixStride = yPlane.getPixelStride(); // usually 1
int uRowStride = uPlane.getRowStride();
int uPixStride = uPlane.getPixelStride(); // usually 2
int vRowStride = vPlane.getRowStride();
int vPixStride = vPlane.getPixelStride(); // usually 2
byte[] out = new byte[width * height * 3];
// Standard BT.601 full-range conversion
// R = 1.164*(Y-16) + 1.596*(V-128)
// G = 1.164*(Y-16) - 0.392*(U-128) - 0.813*(V-128)
// B = 1.164*(Y-16) + 2.017*(U-128)
//
// Use integer math approximation for speed:
// C = Y - 16; D = U - 128; E = V - 128;
// R = (298*C + 409*E + 128) >> 8
// G = (298*C - 100*D - 208*E + 128) >> 8
// B = (298*C + 516*D + 128) >> 8
for (int y = 0; y < height; y++) {
int yRowStart = yRowStride * y;
int uvRow = (y >> 1); // /2
int uRowStart = uRowStride * uvRow;
int vRowStart = vRowStride * uvRow;
for (int x = 0; x < width; x++) {
int yCol = yRowStart + x * yPixStride;
int uvCol = (x >> 1); // /2
int uIndex = uRowStart + uvCol * uPixStride;
int vIndex = vRowStart + uvCol * vPixStride;
int Y = (yBuf.get(yCol) & 0xFF);
int U = (uBuf.get(uIndex) & 0xFF);
int V = (vBuf.get(vIndex) & 0xFF);
int C = Y - 16;
int D = U - 128;
int E = V - 128;
if (C < 0) C = 0;
int R = (298 * C + 409 * E + 128) >> 8;
int G = (298 * C - 100 * D - 208 * E + 128) >> 8;
int B = (298 * C + 516 * D + 128) >> 8;
if (R < 0) R = 0; else if (R > 255) R = 255;
if (G < 0) G = 0; else if (G > 255) G = 255;
if (B < 0) B = 0; else if (B > 255) B = 255;
int outIdx = (y * width + x) * 3;
out[outIdx] = (byte) R;
out[outIdx + 1] = (byte) G;
out[outIdx + 2] = (byte) B;
}
}
return out;
}
// ---- Forward these from your Activity/Unity bridge ----
public void onCreate() {
Log.d(TAG, "CameraPlugin onCreate");
// CustomLifecycle starts at CREATED
// Don't automatically move to RESUMED - let startCamera() handle it
}
public void onResume() {
Log.d(TAG, "CameraPlugin onResume - restarting camera for foreground");
long now = System.currentTimeMillis();
if (isRapidCyclingMode && (now - rapidCyclingStartTime) < RAPID_CYCLING_COOLDOWN) {
Log.d(TAG, "In cooldown; skip resume transition");
return;
}
// Restart camera when app comes to foreground
Log.d(TAG, "Moving camera to RESUMED state (started) for foreground");
mainHandler.post(camLifecycle::toResumed);
}
public void onPause() {
Log.d(TAG, "CameraPlugin onPause - stopping camera for background");
// Don’t post just a lifecycle bump; actually stop now.
if (Looper.myLooper() == Looper.getMainLooper()) {
stopCameraOnMainThread();
} else {
mainHandler.post(this::stopCameraOnMainThread);
}
}
public void onStop() {
Log.d(TAG, "CameraPlugin onStop");
mainHandler.post(camLifecycle::toCreated);
}
public void onDestroy() {
Log.d(TAG, "CameraPlugin onDestroy");
stopCamera();
if (cameraExecutor != null) cameraExecutor.shutdown();
mainHandler.post(camLifecycle::toDestroyed);
}
}
// Explicit lifecycle you control from Unity/Activity
class CustomLifecycle implements LifecycleOwner {
private final LifecycleRegistry lifecycleRegistry;
private final Handler mainHandler = new Handler(Looper.getMainLooper());
public CustomLifecycle() {
lifecycleRegistry = new LifecycleRegistry(this);
// Don't set state in constructor - do it on main thread
}
// Initialize on main thread
public void initialize() {
if (Looper.myLooper() == Looper.getMainLooper()) {
lifecycleRegistry.setCurrentState(Lifecycle.State.CREATED);
} else {
mainHandler.post(() -> lifecycleRegistry.setCurrentState(Lifecycle.State.CREATED));
}
}
public void toResumed() {
if (Looper.myLooper() == Looper.getMainLooper()) {
Log.d("CustomLifecycle", "Setting state to RESUMED");
lifecycleRegistry.setCurrentState(Lifecycle.State.RESUMED);
} else {
mainHandler.post(() -> {
Log.d("CustomLifecycle", "Setting state to RESUMED (posted)");
lifecycleRegistry.setCurrentState(Lifecycle.State.RESUMED);
});
}
}
public void toStarted() {
if (Looper.myLooper() == Looper.getMainLooper()) {
lifecycleRegistry.setCurrentState(Lifecycle.State.STARTED);
} else {
mainHandler.post(() -> lifecycleRegistry.setCurrentState(Lifecycle.State.STARTED));
}
}
public void toCreated() {
if (Looper.myLooper() == Looper.getMainLooper()) {
lifecycleRegistry.setCurrentState(Lifecycle.State.CREATED);
} else {
mainHandler.post(() -> lifecycleRegistry.setCurrentState(Lifecycle.State.CREATED));
}
}
public void toDestroyed() {
if (Looper.myLooper() == Looper.getMainLooper()) {
lifecycleRegistry.setCurrentState(Lifecycle.State.DESTROYED);
} else {
mainHandler.post(() -> lifecycleRegistry.setCurrentState(Lifecycle.State.DESTROYED));
}
}
@Override
public Lifecycle getLifecycle() {
return lifecycleRegistry;
}
}
< /code>
Есть ли какая -нибудь документация для изучения? Или кто -нибудь знает, что я здесь делаю не так?
Подробнее здесь: https://stackoverflow.com/questions/797 ... of-camerax
Как справиться с жизненным циклом камер? ⇐ Android
Форум для тех, кто программирует под Android
1758574252
Anonymous
Я пытаюсь сделать плагин для камеры, который я планирую использовать внутри Unity. Камера, кажется, запускается (также имея некоторые проблемы при установлении разрешения), но когда я помещаю приложение в задний план, камера должна останавливаться, и это происходит, но с этой ошибкой < /p>
2025-09-22 16:17:29.347 30471 30594 Error CameraCaptureSession Session 0: Exception while stopping repeating:
2025-09-22 16:17:29.347 30471 30594 Error CameraCaptureSession android.hardware.camera2.CameraAccessException: CAMERA_ERROR (3): The camera device has encountered a serious error
2025-09-22 16:17:29.347 30471 30594 Error CameraCaptureSession at android.hardware.camera2.impl.CameraDeviceImpl.checkIfCameraClosedOrInError(CameraDeviceImpl.java:2612)
2025-09-22 16:17:29.347 30471 30594 Error CameraCaptureSession at android.hardware.camera2.impl.CameraDeviceImpl.stopRepeating(CameraDeviceImpl.java:1469)
2025-09-22 16:17:29.347 30471 30594 Error CameraCaptureSession at android.hardware.camera2.impl.CameraCaptureSessionImpl.close(CameraCaptureSessionImpl.java:579)
2025-09-22 16:17:29.347 30471 30594 Error CameraCaptureSession at androidx.camera.camera2.internal.SynchronizedCaptureSessionBaseImpl.close(SynchronizedCaptureSessionBaseImpl.java:476)
2025-09-22 16:17:29.347 30471 30594 Error CameraCaptureSession at androidx.camera.camera2.internal.CaptureSession.release(CaptureSession.java:526)
2025-09-22 16:17:29.347 30471 30594 Error CameraCaptureSession at androidx.camera.camera2.internal.Camera2CameraImpl.releaseSession(Camera2CameraImpl.java:555)
2025-09-22 16:17:29.347 30471 30594 Error CameraCaptureSession at androidx.camera.camera2.internal.Camera2CameraImpl.resetCaptureSession(Camera2CameraImpl.java:1329)
2025-09-22 16:17:29.347 30471 30594 Error CameraCaptureSession at androidx.camera.camera2.internal.Camera2CameraImpl.closeCamera(Camera2CameraImpl.java:468)
2025-09-22 16:17:29.347 30471 30594 Error CameraCaptureSession at androidx.camera.camera2.internal.Camera2CameraImpl$StateCallback.handleErrorOnOpen(Camera2CameraImpl.java:1798)
2025-09-22 16:17:29.347 30471 30594 Error CameraCaptureSession at androidx.camera.camera2.internal.Camera2CameraImpl$StateCallback.onError(Camera2CameraImpl.java:1754)
2025-09-22 16:17:29.347 30471 30594 Error CameraCaptureSession at androidx.camera.camera2.internal.CameraDeviceStateCallbacks$ComboDeviceStateCallback.onError(CameraDeviceStateCallbacks.java:121)
2025-09-22 16:17:29.347 30471 30594 Error CameraCaptureSession at android.hardware.camera2.impl.CameraDeviceImpl$ClientStateCallback$4.run(CameraDeviceImpl.java:338)
2025-09-22 16:17:29.347 30471 30594 Error CameraCaptureSession at androidx.camera.core.impl.utils.executor.SequentialExecutor$1.run(SequentialExecutor.java:111)
2025-09-22 16:17:29.347 30471 30594 Error CameraCaptureSession at androidx.camera.core.impl.utils.executor.SequentialExecutor$QueueWorker.workOnQueue(SequentialExecutor.java:231)
2025-09-22 16:17:29.347 30471 30594 Error CameraCaptureSession at androidx.camera.core.impl.utils.executor.SequentialExecutor$QueueWorker.run(SequentialExecutor.java:173)
2025-09-22 16:17:29.347 30471 30594 Error CameraCaptureSession at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
2025-09-22 16:17:29.347 30471 30594 Error CameraCaptureSession at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:644)
> 2025-09-22 16:17:29.347 30471 30594 Error CameraCaptureSession at java.lang.Thread.run(Thread.java:1012)
< /code>
Вот мой код < /p>
package com.android.cameraplugin;
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.ImageFormat;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.util.Size;
import androidx.annotation.NonNull;
import androidx.camera.core.AspectRatio;
import androidx.camera.core.Camera;
import androidx.camera.core.CameraSelector;
import androidx.camera.core.ImageAnalysis;
import androidx.camera.core.ImageProxy;
import androidx.camera.lifecycle.ProcessCameraProvider;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LifecycleRegistry;
import com.google.common.util.concurrent.ListenableFuture;
import java.nio.ByteBuffer;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CameraPlugin {
private static final String TAG = "CameraPlugin";
private static CameraPlugin instance;
private final Activity activity;
private final Context context;
// CameraX components
private ProcessCameraProvider cameraProvider;
private Camera camera;
private ImageAnalysis imageAnalysis;
private CameraSelector cameraSelector;
// Threading
private final ExecutorService cameraExecutor;
private final Handler mainHandler = new Handler(Looper.getMainLooper());
// Our explicit lifecycle for CameraX
private final CustomLifecycle camLifecycle = new CustomLifecycle();
// Camera state
private boolean isCameraStarted = false;
private boolean isFrontCamera = false;
private int targetWidth = 1280;
private int targetHeight = 720;
// Rapid-cycling detection (kept from your original)
private long lastBackgroundTime = 0;
private int backgroundCount = 0;
private static final long BACKGROUND_DETECTION_WINDOW = 5000; // 5 seconds
private boolean isRapidCyclingMode = false;
private long rapidCyclingStartTime = 0;
private static final long RAPID_CYCLING_COOLDOWN = 10000; // 10 seconds
// RGB data storage
private volatile byte[] latestRGBData;
private volatile int frameWidth = 640;
private volatile int frameHeight = 480;
// Image analysis callback (YUV_420_888 guaranteed)
private final ImageAnalysis.Analyzer imageAnalyzer = new ImageAnalysis.Analyzer() {
private long lastProcessTime = 0;
private static final long MIN_PROCESS_INTERVAL = 50; // ~20 FPS max
private int frameSkipCounter = 0;
private static final int FRAME_SKIP_RATIO = 2; // Process every 2nd frame
@Override
public void analyze(@NonNull ImageProxy image) {
try {
// Log first few frames to debug
if (frameSkipCounter < 5) {
Log.d(TAG, "ImageAnalyzer received frame #" + frameSkipCounter +
" - format: " + image.getFormat() +
", size: " + image.getWidth() + "x" + image.getHeight());
}
// Throttle/skip for CPU budget
frameSkipCounter++;
if (frameSkipCounter % FRAME_SKIP_RATIO != 0) return;
long now = System.currentTimeMillis();
if (now - lastProcessTime < MIN_PROCESS_INTERVAL) return;
lastProcessTime = now;
if (image.getFormat() != ImageFormat.YUV_420_888) {
Log.w(TAG, "Unexpected format: " + image.getFormat() + " (expected YUV_420_888)");
}
byte[] rgb = yuv420888ToRgb(image);
if (rgb != null) {
latestRGBData = rgb;
frameWidth = image.getWidth();
frameHeight = image.getHeight();
Log.v(TAG, "Frame " + frameWidth + "x" + frameHeight + " -> RGB bytes=" + rgb.length);
}
} catch (Throwable t) {
Log.e(TAG, "analyze error: " + t.getMessage(), t);
} finally {
image.close();
}
}
};
private CameraPlugin(Activity activity) {
this.activity = activity;
this.context = activity.getApplicationContext();
this.cameraExecutor = Executors.newSingleThreadExecutor();
// Initialize CustomLifecycle on main thread
if (Looper.myLooper() == Looper.getMainLooper()) {
camLifecycle.initialize();
} else {
mainHandler.post(camLifecycle::initialize);
}
initializeCameraProvider();
}
public static CameraPlugin getInstance(Activity activity) {
if (instance == null) {
instance = new CameraPlugin(activity);
}
return instance;
}
private void initializeCameraProvider() {
ListenableFuture
cameraProviderFuture =
ProcessCameraProvider.getInstance(context);
cameraProviderFuture.addListener(() -> {
try {
cameraProvider = cameraProviderFuture.get();
Log.d(TAG, "CameraX provider initialized");
try {
var infos = cameraProvider.getAvailableCameraInfos();
Log.d(TAG, "Available cameras: " + infos.size());
for (int i = 0; i < infos.size(); i++) {
Log.d(TAG, "Camera " + i + ": " + infos.get(i));
}
} catch (Exception e) {
Log.w(TAG, "Could not list camera infos: " + e.getMessage());
}
} catch (ExecutionException | InterruptedException e) {
Log.e(TAG, "Failed to init CameraX provider: " + e.getMessage(), e);
}
}, ContextCompat.getMainExecutor(context));
}
public boolean checkCameraPermission() {
return ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA)
== PackageManager.PERMISSION_GRANTED;
}
public void requestCameraPermission() {
if (!checkCameraPermission()) {
ActivityCompat.requestPermissions(activity,
new String[]{Manifest.permission.CAMERA}, 100);
}
}
public boolean startCamera() {
return startCamera(targetWidth, targetHeight);
}
public boolean startCamera(int targetWidth, int targetHeight) {
Log.d(TAG, "Starting CameraX with " + targetWidth + "x" + targetHeight);
if (isRapidCyclingMode) {
Log.w(TAG, "Blocked: rapid cycling mode");
return false;
}
if (!checkCameraPermission()) {
Log.e(TAG, "Camera permission not granted");
return false;
}
if (cameraProvider == null) {
Log.e(TAG, "Camera provider not ready");
return false;
}
if (isCameraStarted) {
Log.d(TAG, "Camera already started");
return true;
}
if (Looper.myLooper() != Looper.getMainLooper()) {
mainHandler.post(() -> startCameraOnMainThread(targetWidth, targetHeight));
return true;
} else {
return startCameraOnMainThread(targetWidth, targetHeight);
}
}
private boolean startCameraOnMainThread(int targetWidth, int targetHeight) {
try {
this.targetWidth = targetWidth;
this.targetHeight = targetHeight;
Log.d(TAG, "Requesting camera resolution: " + targetWidth + "x" + targetHeight);
cameraSelector = new CameraSelector.Builder()
.requireLensFacing(isFrontCamera ? CameraSelector.LENS_FACING_FRONT
: CameraSelector.LENS_FACING_BACK)
.build();
// Force a standard resolution that's commonly supported
// Use 1280x720 (HD) which is almost universally supported
Size targetSize = new Size(1280, 720);
Log.d(TAG, "Forcing target size to: " + targetSize + " (ignoring requested " + targetWidth + "x" + targetHeight + ")");
imageAnalysis = new ImageAnalysis.Builder()
.setTargetResolution(targetSize)
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
// Force YUV_420_888 output to the analyzer
.setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_YUV_420_888)
.build();
imageAnalysis.setAnalyzer(cameraExecutor, imageAnalyzer);
cameraProvider.unbindAll();
try {
camera = cameraProvider.bindToLifecycle(
camLifecycle,
cameraSelector,
imageAnalysis
);
// Move custom lifecycle to RESUMED state to actually start the camera
mainHandler.post(camLifecycle::toResumed);
isCameraStarted = true;
Log.d(TAG, "CameraX started (YUV_420_888, analysis only)");
return true;
} catch (Exception bindingError) {
Log.e(TAG, "Failed to bind camera to lifecycle: " + bindingError.getMessage(), bindingError);
// Clean up on binding failure
if (cameraProvider != null) {
try {
cameraProvider.unbindAll();
} catch (Exception cleanupError) {
Log.e(TAG, "Cleanup error after binding failure: " + cleanupError.getMessage());
}
}
return false;
}
} catch (Exception e) {
Log.e(TAG, "Failed to start CameraX: " + e.getMessage(), e);
return false;
}
}
public void stopCamera() {
Log.d(TAG, "Stopping CameraX");
if (Looper.myLooper() != Looper.getMainLooper()) {
mainHandler.post(this::stopCameraOnMainThread);
} else {
stopCameraOnMainThread();
}
}
private void stopCameraOnMainThread() {
Log.d(TAG, "Stopping CameraX (sync)");
// 0) Prevent new analyze callbacks while tearing down
try {
if (imageAnalysis != null) imageAnalysis.clearAnalyzer();
} catch (Exception ignore) {}
// 1) Unbind all use cases NOW (not posted)
if (cameraProvider != null) {
try { cameraProvider.unbindAll(); } catch (Exception e) {
Log.e(TAG, "unbindAll error: " + e.getMessage(), e);
}
}
// 2) Drive lifecycle down NOW (not posted)
try { camLifecycle.toCreated(); } catch (Exception e) {
Log.e(TAG, "Error setting lifecycle to CREATED: " + e.getMessage());
}
// 3) Null state
isCameraStarted = false;
camera = null;
imageAnalysis = null;
Log.d(TAG, "CameraX stopped");
}
private void stopCameraImmediately() {
Log.d(TAG, "Immediate stop (break rapid cycling)");
if (cameraProvider != null) {
try {
Log.d(TAG, "Immediate stop - unbinding all use cases");
cameraProvider.unbindAll();
} catch (Exception e) {
Log.e(TAG, "Immediate unbindAll error: " + e.getMessage(), e);
}
}
// Move custom lifecycle to CREATED state immediately
try {
if (Looper.myLooper() == Looper.getMainLooper()) {
camLifecycle.toCreated();
} else {
mainHandler.post(camLifecycle::toCreated);
}
} catch (Exception e) {
Log.e(TAG, "Error setting lifecycle to CREATED (immediate): " + e.getMessage());
}
isCameraStarted = false;
camera = null;
imageAnalysis = null;
}
public void pauseCamera() { Log.d(TAG, "pauseCamera (lifecycle-driven)"); }
public void resumeCamera() { Log.d(TAG, "resumeCamera (lifecycle-driven)"); }
public void switchCamera() {
Log.d(TAG, "Switching camera");
if (isCameraStarted) {
stopCamera();
isFrontCamera = !isFrontCamera;
startCamera();
} else {
isFrontCamera = !isFrontCamera;
}
}
public boolean isUsingFrontCamera() { return isFrontCamera; }
public boolean isCameraRunning() { return isCameraStarted && camera != null; }
// Unity-facing frame accessors
public boolean hasNewFrame() { return latestRGBData != null; }
public byte[] getLatestFrameData() { return latestRGBData; }
public byte[] getLatestRGBFrame() { return latestRGBData; }
public int getFrameWidth() { return frameWidth; }
public int getFrameHeight() { return frameHeight; }
public void clearFrameData() { latestRGBData = null; }
public boolean isCameraInitialized() { return isCameraStarted; }
public boolean isCameraReady() { return isCameraStarted && camera != null && latestRGBData != null; }
public void setTargetResolution(int width, int height) {
this.targetWidth = width;
this.targetHeight = height;
}
public int getTargetWidth() { return targetWidth; }
public int getTargetHeight() { return targetHeight; }
public void setZoom(float zoomLevel) {
Log.d(TAG, "setZoom called with: " + zoomLevel);
// Implement via CameraControl if/when needed
}
public void switchCamera(String direction) {
Log.d(TAG, "switchCamera(direction=" + direction + ")");
switchCamera();
}
public void breakRapidCycling() {
Log.d(TAG, "breakRapidCycling called");
backgroundCount = 0;
lastBackgroundTime = 0;
isRapidCyclingMode = false;
rapidCyclingStartTime = 0;
if (isCameraStarted) stopCameraImmediately();
}
public boolean isInRapidCycling() { return isRapidCyclingMode; }
// Manual camera control methods
public void startCameraManually() {
Log.d(TAG, "Manually starting camera via custom lifecycle");
if (!isRapidCyclingMode) {
mainHandler.post(camLifecycle::toResumed);
}
}
public void stopCameraManually() {
Log.d(TAG, "Manually stopping camera via custom lifecycle");
mainHandler.post(camLifecycle::toStarted);
}
// Handle camera errors and recovery
public void handleCameraError() {
Log.e(TAG, "Handling camera error - stopping and resetting");
// Stop camera immediately
stopCameraImmediately();
// Reset rapid cycling mode
isRapidCyclingMode = false;
backgroundCount = 0;
lastBackgroundTime = 0;
rapidCyclingStartTime = 0;
// Wait a bit before allowing restart
mainHandler.postDelayed(() -> {
Log.d(TAG, "Camera error recovery - ready for restart");
}, 2000); // 2 second delay
}
// --------- YUV_420_888 -> RGB (interleaved) WITHOUT JPEG ----------
// Handles arbitrary row/pixel strides for Y, U, V planes.
private static byte[] yuv420888ToRgb(ImageProxy image) {
final int width = image.getWidth();
final int height = image.getHeight();
ImageProxy.PlaneProxy yPlane = image.getPlanes()[0];
ImageProxy.PlaneProxy uPlane = image.getPlanes()[1];
ImageProxy.PlaneProxy vPlane = image.getPlanes()[2];
ByteBuffer yBuf = yPlane.getBuffer();
ByteBuffer uBuf = uPlane.getBuffer();
ByteBuffer vBuf = vPlane.getBuffer();
int yRowStride = yPlane.getRowStride();
int yPixStride = yPlane.getPixelStride(); // usually 1
int uRowStride = uPlane.getRowStride();
int uPixStride = uPlane.getPixelStride(); // usually 2
int vRowStride = vPlane.getRowStride();
int vPixStride = vPlane.getPixelStride(); // usually 2
byte[] out = new byte[width * height * 3];
// Standard BT.601 full-range conversion
// R = 1.164*(Y-16) + 1.596*(V-128)
// G = 1.164*(Y-16) - 0.392*(U-128) - 0.813*(V-128)
// B = 1.164*(Y-16) + 2.017*(U-128)
//
// Use integer math approximation for speed:
// C = Y - 16; D = U - 128; E = V - 128;
// R = (298*C + 409*E + 128) >> 8
// G = (298*C - 100*D - 208*E + 128) >> 8
// B = (298*C + 516*D + 128) >> 8
for (int y = 0; y < height; y++) {
int yRowStart = yRowStride * y;
int uvRow = (y >> 1); // /2
int uRowStart = uRowStride * uvRow;
int vRowStart = vRowStride * uvRow;
for (int x = 0; x < width; x++) {
int yCol = yRowStart + x * yPixStride;
int uvCol = (x >> 1); // /2
int uIndex = uRowStart + uvCol * uPixStride;
int vIndex = vRowStart + uvCol * vPixStride;
int Y = (yBuf.get(yCol) & 0xFF);
int U = (uBuf.get(uIndex) & 0xFF);
int V = (vBuf.get(vIndex) & 0xFF);
int C = Y - 16;
int D = U - 128;
int E = V - 128;
if (C < 0) C = 0;
int R = (298 * C + 409 * E + 128) >> 8;
int G = (298 * C - 100 * D - 208 * E + 128) >> 8;
int B = (298 * C + 516 * D + 128) >> 8;
if (R < 0) R = 0; else if (R > 255) R = 255;
if (G < 0) G = 0; else if (G > 255) G = 255;
if (B < 0) B = 0; else if (B > 255) B = 255;
int outIdx = (y * width + x) * 3;
out[outIdx] = (byte) R;
out[outIdx + 1] = (byte) G;
out[outIdx + 2] = (byte) B;
}
}
return out;
}
// ---- Forward these from your Activity/Unity bridge ----
public void onCreate() {
Log.d(TAG, "CameraPlugin onCreate");
// CustomLifecycle starts at CREATED
// Don't automatically move to RESUMED - let startCamera() handle it
}
public void onResume() {
Log.d(TAG, "CameraPlugin onResume - restarting camera for foreground");
long now = System.currentTimeMillis();
if (isRapidCyclingMode && (now - rapidCyclingStartTime) < RAPID_CYCLING_COOLDOWN) {
Log.d(TAG, "In cooldown; skip resume transition");
return;
}
// Restart camera when app comes to foreground
Log.d(TAG, "Moving camera to RESUMED state (started) for foreground");
mainHandler.post(camLifecycle::toResumed);
}
public void onPause() {
Log.d(TAG, "CameraPlugin onPause - stopping camera for background");
// Don’t post just a lifecycle bump; actually stop now.
if (Looper.myLooper() == Looper.getMainLooper()) {
stopCameraOnMainThread();
} else {
mainHandler.post(this::stopCameraOnMainThread);
}
}
public void onStop() {
Log.d(TAG, "CameraPlugin onStop");
mainHandler.post(camLifecycle::toCreated);
}
public void onDestroy() {
Log.d(TAG, "CameraPlugin onDestroy");
stopCamera();
if (cameraExecutor != null) cameraExecutor.shutdown();
mainHandler.post(camLifecycle::toDestroyed);
}
}
// Explicit lifecycle you control from Unity/Activity
class CustomLifecycle implements LifecycleOwner {
private final LifecycleRegistry lifecycleRegistry;
private final Handler mainHandler = new Handler(Looper.getMainLooper());
public CustomLifecycle() {
lifecycleRegistry = new LifecycleRegistry(this);
// Don't set state in constructor - do it on main thread
}
// Initialize on main thread
public void initialize() {
if (Looper.myLooper() == Looper.getMainLooper()) {
lifecycleRegistry.setCurrentState(Lifecycle.State.CREATED);
} else {
mainHandler.post(() -> lifecycleRegistry.setCurrentState(Lifecycle.State.CREATED));
}
}
public void toResumed() {
if (Looper.myLooper() == Looper.getMainLooper()) {
Log.d("CustomLifecycle", "Setting state to RESUMED");
lifecycleRegistry.setCurrentState(Lifecycle.State.RESUMED);
} else {
mainHandler.post(() -> {
Log.d("CustomLifecycle", "Setting state to RESUMED (posted)");
lifecycleRegistry.setCurrentState(Lifecycle.State.RESUMED);
});
}
}
public void toStarted() {
if (Looper.myLooper() == Looper.getMainLooper()) {
lifecycleRegistry.setCurrentState(Lifecycle.State.STARTED);
} else {
mainHandler.post(() -> lifecycleRegistry.setCurrentState(Lifecycle.State.STARTED));
}
}
public void toCreated() {
if (Looper.myLooper() == Looper.getMainLooper()) {
lifecycleRegistry.setCurrentState(Lifecycle.State.CREATED);
} else {
mainHandler.post(() -> lifecycleRegistry.setCurrentState(Lifecycle.State.CREATED));
}
}
public void toDestroyed() {
if (Looper.myLooper() == Looper.getMainLooper()) {
lifecycleRegistry.setCurrentState(Lifecycle.State.DESTROYED);
} else {
mainHandler.post(() -> lifecycleRegistry.setCurrentState(Lifecycle.State.DESTROYED));
}
}
@Override
public Lifecycle getLifecycle() {
return lifecycleRegistry;
}
}
< /code>
Есть ли какая -нибудь документация для изучения? Или кто -нибудь знает, что я здесь делаю не так?
Подробнее здесь: [url]https://stackoverflow.com/questions/79772043/how-to-handle-lifecycle-of-camerax[/url]
Ответить
1 сообщение
• Страница 1 из 1
Перейти
- Кемерово-IT
- ↳ Javascript
- ↳ C#
- ↳ JAVA
- ↳ Elasticsearch aggregation
- ↳ Python
- ↳ Php
- ↳ Android
- ↳ Html
- ↳ Jquery
- ↳ C++
- ↳ IOS
- ↳ CSS
- ↳ Excel
- ↳ Linux
- ↳ Apache
- ↳ MySql
- Детский мир
- Для души
- ↳ Музыкальные инструменты даром
- ↳ Печатная продукция даром
- Внешняя красота и здоровье
- ↳ Одежда и обувь для взрослых даром
- ↳ Товары для здоровья
- ↳ Физкультура и спорт
- Техника - даром!
- ↳ Автомобилистам
- ↳ Компьютерная техника
- ↳ Плиты: газовые и электрические
- ↳ Холодильники
- ↳ Стиральные машины
- ↳ Телевизоры
- ↳ Телефоны, смартфоны, плашеты
- ↳ Швейные машинки
- ↳ Прочая электроника и техника
- ↳ Фототехника
- Ремонт и интерьер
- ↳ Стройматериалы, инструмент
- ↳ Мебель и предметы интерьера даром
- ↳ Cантехника
- Другие темы
- ↳ Разное даром
- ↳ Давай меняться!
- ↳ Отдам\возьму за копеечку
- ↳ Работа и подработка в Кемерове
- ↳ Давай с тобой поговорим...
Мобильная версия