How to choose a specific physical camera with Camerax API?

263 views
Skip to first unread message
Assigned to tonyt...@google.com by charco...@google.com

jun jiang

unread,
May 8, 2025, 10:06:39 PMMay 8
to Android CameraX Discussion Group
Hi, experts of Camerax!

I'm a developer of a camera APP with camerax. Because I need to take photos in macro mode to get more detail features of the subject, I need to choose a specific physical camera to take photo, e.g. the telephoto camera.

I'm using a Sumsung Galaxy S23 Ultra model to test. There are four physical lens in S23, which are the ultra-wide、wide、telephoto(3x)、telephoto(10x) respectively.

I used cameraManager.getCameraIdList() to get the logical cameras. The logical camera with ID "0" is composed of 4 physical cameras(IDs are 2,5,6,7).

I have tried two ways to set the specific physical camera, but still got failed. There was no effect, the defalt camera was always used.

Method 1:

I used the setPhysicalCameraId() of the CameraSelector.Builder() to set the specific physical camera(e.g. the physical camera "5"). The api was added in camerax 1.4.0. The camerax version I used is v1.4.2.

for (String id : logicCameraIds) {
                    CameraCharacteristics cameraCharacteristics = cameraManager.getCameraCharacteristics(id);
                    Set<String> physicalCameraIds = cameraCharacteristics.getPhysicalCameraIds();

                    if (physicalCameraIds.contains(targetPhysicalCameraId)) {
                        targetLogicalCameraId = id;
                        return new CameraSelector.Builder()
                                .addCameraFilter(cameraInfos -> {
                                    return cameraInfos.stream().filter(camInfo -> {
                                        String thisCamId = Camera2CameraInfo.from(camInfo).getCameraId();
                                        return thisCamId.equals(id);
                                    }).collect(Collectors.toList());
                                })
                                .setPhysicalCameraId(targetPhysicalCameraId)
                                .build();
                    }
                    else{
                        continue;
                    }
                }

Method 2:

I used the  Camera2Interop.Extender.setPhysicalCameraId to set the specific physical camera.  Firstly, I got the cameraselector with the logical camera "0", then set the specific physical camera "5" with the Camera2Interop.Extender.setPhysicalCameraId.

Preview.Builder previewBuilder = new Preview.Builder();
            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
               Camera2Interop.Extender previewExtender = new Camera2Interop.Extender(previewBuilder).setPhysicalCameraId(targetPhysicalCamera);
            }
Preview preview = previewBuilder
                    //.setPreviewStabilizationEnabled(true)
                    .build();
preview.setSurfaceProvider(viewBinding.viewFinder.getSurfaceProvider());


ImageCapture.Builder imageCaptureBuilder = new ImageCapture.Builder();
           if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
               Camera2Interop.Extender imageCaptureExtender = new Camera2Interop.Extender(imageCaptureBuilder).setPhysicalCameraId(targetPhysicalCamera);
            }
imageCapture = imageCaptureBuilder.build();

Unfortunately, there was no effect with the two methods above, the default camera was used and it can't switch to the  specific physical camera.

Is there anyone could tell me have to switch to specific physical camera? This function is very common for users, so I suggest to consider adding the function to the camerax.  

tonyt...@google.com

unread,
May 14, 2025, 1:50:48 AMMay 14
to Android CameraX Discussion Group, justi...@gmail.com
Hi, thanks for the question. So to be clear, are camera ID [2, 5, 6, 7] contained in CameraCharacteristics.getPhysicalCameraIds of camera 0?

jun jiang

unread,
May 14, 2025, 7:45:43 AMMay 14
to Android CameraX Discussion Group, tonyt...@google.com, jun jiang
Thanks for your reply!

Yes, the physical camera ID [2, 5, 6, 7] are contained in CameraCharacteristics.getPhysicalCameraIds of the logical camera 0.

I have tried to implement the function mentioned above with the camera2 framework. The result is that I can switch to the specific physical camera freely with no problem. I used the OutputConfiguration.setPhysicalCameraId(id) to set the physical camera.

However, it's weird that there is no effect with CameraX API. Could help me to check the problem. Thanks!

jun jiang

unread,
May 28, 2025, 10:25:56 PMMay 28
to Android CameraX Discussion Group, jun jiang, tonyt...@google.com
I'm still struggling with this problem. Is there anybody can help me to sovle this problem? Thanks!~

Scott Nien

unread,
May 29, 2025, 12:09:24 AMMay 29
to jun jiang, Android CameraX Discussion Group, tonyt...@google.com
Do you bind any other use cases ?  

To make the physicalCameraId works,  all use case needs to have physicalCameraId being set using Camera2Interop.Extender

--
You received this message because you are subscribed to the Google Groups "Android CameraX Discussion Group" group.
To unsubscribe from this group and stop receiving emails from it, send an email to camerax-develop...@android.com.
To view this discussion visit https://groups.google.com/a/android.com/d/msgid/camerax-developers/714dbc61-39af-44b4-8d49-ef7a46ab0f4bn%40android.com.

jun jiang

unread,
Jun 2, 2025, 11:14:05 PMJun 2
to Android CameraX Discussion Group, scot...@google.com, Android CameraX Discussion Group, tonyt...@google.com, jun jiang
I use the Preview and ImageCapture two use cases, in which the physicalCameraId have been set using Camera2Interop.Extender both.

Is the physical multi-camera switching function behaving normally in your use case  using Camera2Interop.Extender?  

Scott Nien

unread,
Jun 5, 2025, 12:17:57 AMJun 5
to jun jiang, Android CameraX Discussion Group, tonyt...@google.com
Hi Jun, 

Can you show your code snippet on how you set up the use cases to enable the physical camera id ?  


jun jiang

unread,
Jun 19, 2025, 10:16:47 PMJun 19
to Android CameraX Discussion Group, scot...@google.com, Android CameraX Discussion Group, tonyt...@google.com, jun jiang
Of course. Here is my coe snippet:

private void startCamera() {
        viewBinding.viewFinder.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                singleTapFocus(event);
                v.performClick();
                return true;
            }
        });
        try {
            CameraManager cameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
            for (String cameraId : cameraManager.getCameraIdList()) {
                CameraCharacteristics cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraId);
                StreamConfigurationMap configurationMap = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
//                Size[] outputSizes = configurationMap.getOutputSizes(ImageFormat.YUV_420_888);
                Size[] outputSizes = configurationMap.getOutputSizes(ImageFormat.PRIVATE);
                for (Size outputSize : outputSizes) {
                    Log.d("TAG", "cameraId:"+cameraId+",支持的分辨率:"+outputSize);
                }

            }
        }catch (Exception e){
            e.printStackTrace();
        }
        // 将Camera的生命周期和Activity绑定在一起(设定生命周期所有者),这样就不用手动控制相机的启动和关闭。
        ListenableFuture<ProcessCameraProvider> cameraProviderFuture = ProcessCameraProvider.getInstance(this);

        cameraProviderFuture.addListener(() -> {
            try {
                // 将你的相机和当前生命周期的所有者绑定所需的对象
                processCameraProvider = cameraProviderFuture.get();

                bindCamera(processCameraProvider);
                // 创建一个Preview 实例,并设置该实例的 surface

            } catch (Exception e) {
                Log.e(Configuration.TAG, "用例绑定失败!" + e);
            }
        }, ContextCompat.getMainExecutor(this));

    }


@OptIn(markerClass = ExperimentalCamera2Interop.class)
    private void bindCamera(ProcessCameraProvider processCameraProvider) {
        try {

            //选择特定摄像头
            String targetPhysicalCamera = "7";
            CameraSelector cameraSelector;
            if(MainActivity.GLOBAL_SELECTED_CAMERA.equals("Default") || !SettingActivity.GLOBAL_DEVICE_CAMERA){
                cameraSelector = selectExternalOrBestCamera(processCameraProvider);
                viewBinding.switchCameraButton.setEnabled(true);
            }
            else{
                cameraSelector = selectLogicalCameraWithPhysicalCameraID(targetPhysicalCamera);
                viewBinding.switchCameraButton.setEnabled(false);
            }

            // 创建一个Preview 实例,并设置该实例的 surface 提供者(provider)。

            Preview.Builder previewBuilder = new Preview.Builder();
            Preview preview = previewBuilder
                    //.setPreviewStabilizationEnabled(true)
                    .build();
            preview.setSurfaceProvider(viewBinding.viewFinder.getSurfaceProvider());


            // 创建拍照所需的实例

            ImageCapture.Builder imageCaptureBuilder = new ImageCapture.Builder();

            if(!MainActivity.GLOBAL_SELECTED_CAMERA.equals("Default") && SettingActivity.GLOBAL_DEVICE_CAMERA){
                // 通过 Camera2Interop 设置物理摄像头 ID
                Camera2Interop.Extender imageCaptureExtender =
                        new Camera2Interop.Extender(imageCaptureBuilder)
                                .setPhysicalCameraId(targetPhysicalCamera);

            }
            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
                Camera2Interop.Extender imageCaptureExtender = new Camera2Interop.Extender(imageCaptureBuilder).setPhysicalCameraId(targetPhysicalCamera);
            }
            imageCapture = imageCaptureBuilder.build();






            Boolean isPreviewStabilizationSupported = Preview.getPreviewCapabilities(processCameraProvider.getCameraInfo(cameraSelector)).isStabilizationSupported();

            // 设置预览帧分析
            ImageAnalysis imageAnalysis = new ImageAnalysis.Builder()
                    .setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_RGBA_8888)
                    .setResolutionSelector(new ResolutionSelector.Builder().setResolutionStrategy(ResolutionStrategy.HIGHEST_AVAILABLE_STRATEGY).build())
                    .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
                    .build();
            imageAnalysis.setAnalyzer(cameraExecutor, new ImageAnalysis.Analyzer() {
                @SuppressLint("UnsafeOptInUsageError")
                @Override
                public void analyze(@NonNull ImageProxy image) {
                    Log.d(Configuration.TAG, "Image's stamp is " +
                            "cameraId:"+Camera2CameraInfo.from(camera.getCameraInfo()).getCameraId()+
                            Objects.requireNonNull(image.getImage()).getTimestamp() +
                            "; 尺寸:" + image.getWidth() + "*" + image.getHeight()+
                            ";\nformat:"+image.getFormat());
                    Log.d("TAG", "startCatptureImages:"+startCatptureImages);

                    long start  = System.currentTimeMillis();

                    try {

                        if (image == null || image.getImage() == null) {
                            return;
                        }

                       
                }
            });

            // 重新绑定用例前先解绑
            processCameraProvider.unbindAll();


            camera = processCameraProvider.bindToLifecycle(MultiActivity.this,
                    cameraSelector,
                    preview,
                    imageCapture,
                    imageAnalysis/*,
                        videoCapture*/);


            //开启防抖
            toggleStabilization(previewBuilder);

            viewBinding.swFlashlight.setChecked(true);


            // 设置定时器每隔1秒自动对焦
            //startAutoFocus();
            startAutoFocusLoop();


        } catch (Exception e) {
            Log.e(Configuration.TAG, "用例绑定失败!" + e);
        }
    }


    private int count = -1;
    //选择特定摄像头
    @SuppressLint("UnsafeOptInUsageError")
    public CameraSelector selectExternalOrBestCamera(ProcessCameraProvider provider) {
        //这里获取设备的摄像头信息列表,里面就包含有多少个摄像头
        List<Camera2CameraInfo> cam2Infos = provider.getAvailableCameraInfos().stream().map(it -> {
            return Camera2CameraInfo.from(it);
        }).collect(Collectors.toList());

        if (cam2Infos.isEmpty()){
            return CameraSelector.DEFAULT_BACK_CAMERA;
        }else {
            count++;
            if (count>=cam2Infos.size()) count = 0;

            return new CameraSelector.Builder()
                    .addCameraFilter(cameraInfos -> {
                        return cameraInfos.stream().filter(camInfo -> {
                            String thisCamId = Camera2CameraInfo.from(camInfo).getCameraId();
//                            Log.d(TAG, "selectExternalOrBestCamera: ${cam2Infos[count].cameraId},${camInfo.implementationType}")
                            viewBinding.tvMsg.setText("切换摄像头:"+cam2Infos.get(count).getCameraId()+"/"+cam2Infos.size());
                            return thisCamId.equals(cam2Infos.get(count).getCameraId());
                        }).collect(Collectors.toList());
                    })
                    .build();

        }

    }


    public CameraSelector selectLogicalCameraWithPhysicalCameraID(String targetPhysicalCameraId) {
        try {
            cameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
            String[] cameraIds = cameraManager.getCameraIdList();
            List<String> backCameraIds = new ArrayList<>();
            for (String id : cameraIds) {
                CameraCharacteristics cameraCharacteristics = cameraManager.getCameraCharacteristics(id);
                if (cameraCharacteristics.get(CameraCharacteristics.LENS_FACING) != CameraCharacteristics.LENS_FACING_BACK)
                    continue;

                backCameraIds.add(id);
            }

            String targetLogicalCameraId = null;
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
                List<String> logicCameraIds = new ArrayList<>();
                for (String id : backCameraIds) {

                    CameraCharacteristics cameraCharacteristics = cameraManager.getCameraCharacteristics(id);
                    Set<String> physicalCameraIds = cameraCharacteristics.getPhysicalCameraIds();
                    int[] available = cameraCharacteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
                    for (int i : available) {
                        if (i == CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA) {
                            logicCameraIds.add(id);

                        }
                    }
                }

                for (String id : logicCameraIds) {
                    CameraCharacteristics cameraCharacteristics = cameraManager.getCameraCharacteristics(id);
                    Set<String> physicalCameraIds = cameraCharacteristics.getPhysicalCameraIds();

                    if (physicalCameraIds.contains(targetPhysicalCameraId)) {
                        targetLogicalCameraId = id;
                        return new CameraSelector.Builder()
                                .addCameraFilter(cameraInfos -> {
                                    return cameraInfos.stream().filter(camInfo -> {
                                        String thisCamId = Camera2CameraInfo.from(camInfo).getCameraId();
                                        return thisCamId.equals(id);
                                    }).collect(Collectors.toList());
                                })
                                .setPhysicalCameraId(targetPhysicalCameraId)
                                .build();
                    }
                    else{
                        continue;
                    }
                }
            }



        }
        catch (Exception e){
            e.printStackTrace();
        }

        return CameraSelector.DEFAULT_BACK_CAMERA;

    }

    @OptIn(markerClass = ExperimentalCamera2Interop.class)
    @SuppressLint("UnsafeExperimentalUsageError")
    private CameraSelector getCameraSelector(String cameraId) {
        return new CameraSelector.Builder()
                .addCameraFilter(cameras -> {
                    List<CameraInfo> result = new ArrayList<>();
                    for (CameraInfo cameraInfo : cameras) {
                        if (cameraId.equals(Camera2CameraInfo.from(cameraInfo).getCameraId())) {
                            result.add(cameraInfo);
                        }
                    }
                    return result;
                })
                .build();
    }


private void switchCamera(){
        //切换摄像头
        ;
        bindCamera(processCameraProvider);

    }

Scott Nien

unread,
Jun 19, 2025, 11:07:01 PMJun 19
to jun jiang, Android CameraX Discussion Group, tonyt...@google.com
Hi , 

I saw that you just set the physicalCameraId on ImageCapture. Can you try to set the physicalCameraId in Preview and ImageAnalysis and see if that works ?

jun jiang

unread,
Jun 20, 2025, 11:14:19 AMJun 20
to Android CameraX Discussion Group, scot...@google.com, Android CameraX Discussion Group, tonyt...@google.com, jun jiang
Thanks for the reminder. I finally located the problem. I only  set the physicalCameraId on ImageCapture and Preview. I forgot to set  the physicalCameraId on ImageAnalysis.
Just as you said, it need to be setted on all  use cases.

It's just a little bit complicated to use. It would be better if there would be a simpler way to switch to different lens in the future version of CameraX.

Thanks for your help!~

Scott Nien

unread,
Jun 23, 2025, 12:32:57 AMJun 23
to jun jiang, Android CameraX Discussion Group, tonyt...@google.com
You are right, the way to set the physicalCameraId is not easy to use.  You need to set physicalCameraId via Camera2Extender on all use cases to make it work. 
We will improve it. 
Reply all
Reply to author
Forward
0 new messages