Android-CameraX: Switch between multiple front cameras

Viewed 4863

I´m trying to write my first Android Camera App, but it always selects the zoom camera instead of the main camera. (I tested it on a Huawei P30 Pro)

And the code is based on the offical camerax sample application (https://github.com/android/camera-samples/tree/master/CameraXBasic)

The relevant code:

/** Declare and bind preview, capture and analysis use cases */
private fun bindCameraUseCases() {

    // Get screen metrics used to setup camera for full screen resolution
    val metrics = DisplayMetrics().also { viewFinder.display.getRealMetrics(it) }
    Log.d(TAG, "Screen metrics: ${metrics.widthPixels} x ${metrics.heightPixels}")

    val screenAspectRatio = aspectRatio(metrics.widthPixels, metrics.heightPixels)
    Log.d(TAG, "Preview aspect ratio: $screenAspectRatio")

    val rotation = viewFinder.display.rotation

    // Bind the CameraProvider to the LifeCycleOwner

    val cameraSelector = CameraSelector.Builder().requireLensFacing(lensFacing).build()

    val cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext())
    cameraProviderFuture.addListener(Runnable {

        // CameraProvider
        val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()

        // Preview
        preview = Preview.Builder()
            // We request aspect ratio but no resolution
            .setTargetAspectRatio(screenAspectRatio)
            // Set initial target rotation
            .setTargetRotation(rotation)
            .build()

        // ImageCapture
        imageCapture = ImageCapture.Builder()
            .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
            // We request aspect ratio but no resolution to match preview config, but letting
            // CameraX optimize for whatever specific resolution best fits our use cases
            .setTargetAspectRatio(screenAspectRatio)
            // Set initial target rotation, we will have to call this again if rotation changes
            // during the lifecycle of this use case
            .setTargetRotation(rotation)
            .build()

        // Must unbind the use-cases before rebinding them
        cameraProvider.unbindAll()

        try {
            // A variable number of use-cases can be passed here -
            // camera provides access to CameraControl & CameraInfo
            camera = cameraProvider.bindToLifecycle(
                    this, cameraSelector, preview, imageCapture /**, imageAnalyzer*/)

            // Attach the viewfinder's surface provider to preview use case
            preview?.setSurfaceProvider(viewFinder.createSurfaceProvider(camera?.cameraInfo))
        } catch(exc: Exception) {
            Log.e(TAG, "Use case binding failed", exc)
        }

    }, ContextCompat.getMainExecutor(requireContext()))
}
3 Answers

With the current CameraX APIs, I don't think you can choose which specific camera to use, you can only select the lens facing (CameraSelector.Builder().requireLensFacing(int)), which can either be front or back. When you bind use cases, CameraX chooses the first camera that meets the use cases requirements. For example, 2 different back cameras may be used when binding the preview use case with extensions enabled vs disabled.

The CameraSelector API seems to still have room to grow, so in the future, you may have more control over which camera is selected. If you need that kind of control right now, you may need to use Camera2.

I have a similar issue but with the back camera. I checked some devices and found that on all my tested device I can select the camera id and get the right camera:

  • Camera ID "0" -> Normal/default back facing camera
  • Camera ID "1" -> Normal/default front facing camera

So I created a custom CameraFilter where I filter for camera id "0":

class CameraIdFilter(private val cameraId: String) : CameraFilter {

override fun filterCameras(cameras: Set<CameraInternal>): Set<CameraInternal> {
    val resultCameras: MutableSet<CameraInternal> = LinkedHashSet()
    for (camera in cameras) {
        val cameraId = camera.cameraInfoInternal.cameraId
        if (cameraId == this.cameraId) {
            resultCameras.add(camera)
        }
    }
    return resultCameras
}

}

And append the filter to the camera selector:

        val cameraSelectorBuilder = CameraSelector.Builder()
            .requireLensFacing(lensFacing)
            .appendFilter(CameraIdFilter("0"))

I don't recommend using this in a production app, because I'm sure there are some devices that don't follow this convention/pattern. But you can use it for an internal app where you know which devices your users use

You can change camera by setting cameraSelector. For example, to set the camera with index=2, use this code.

List<CameraInfo> availableCameraInfos = cameraProvider.getAvailableCameraInfos();
Log.i(TAG, "[startCamera] available cameras:"+availableCameraInfos);
    
// Select back camera as a default
//CameraSelector cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA;
CameraSelector cameraSelector = availableCameraInfos.get(2).getCameraSelector();
Related