Multi-camera API

توجه: این صفحه به بسته Camera2 اشاره دارد. مگر اینکه برنامه شما به ویژگی‌های خاص و سطح پایین Camera2 نیاز داشته باشد، توصیه می‌کنیم از CameraX استفاده کنید. هم CameraX و هم Camera2 از Android 5.0 (سطح API 21) و بالاتر پشتیبانی می‌کنند.

ویژگی چنددوربینی با Android 9 (سطح میانای برنامه‌سازی کاربردی ۲۸) معرفی شد. از زمان انتشار آن، دستگاه‌هایی به بازار آمده‌اند که از این API پشتیبانی می‌کنند. بسیاری از موارد استفاده از چند دوربین با پیکربندی سخت‌افزاری خاصی جفت می‌شوند. به‌عبارت دیگر، همه موارد استفاده با همه دستگاه‌ها سازگار نیستند، که این امر باعث می‌شود ویژگی‌های چنددوربینی گزینه خوبی برای ارائه ویژگی Play باشد.

برخی‌از موارد استفاده معمول عبارت‌اند از:

  • بزرگ‌نمایی: جابه‌جایی بین دوربین‌ها بسته به ناحیه برش یا فاصله کانونی موردنظر.
  • عمق: استفاده از چند دوربین برای ساختن نقشه عمق.
  • بوکه: استفاده از اطلاعات عمق استنباطی برای شبیه‌سازی محدوده فوکوس باریک شبیه به دوربین DSLR.

تفاوت بین دوربین‌های منطقی و فیزیکی

درک API چنددوربینی مستلزم درک تفاوت بین دوربین‌های منطقی و فیزیکی است. برای مرجع، دستگاهی با سه دوربین پشت را درنظر بگیرید. در این مثال، هریک از سه دوربین پشت به‌عنوان دوربین فیزیکی درنظر گرفته می‌شود. دوربین منطقی گروهی از دو یا چند دوربین فیزیکی است. برونداد دوربین منطقی می‌تواند جاری‌سازی‌ای باشد که از یکی از دوربین‌های فیزیکی زیرین می‌آید، یا جاری‌سازی ترکیبی که به‌طور هم‌زمان از بیش از یک دوربین فیزیکی زیرین می‌آید. در هرصورت، جاری‌سازی توسط لایه انتزاعی سخت‌افزار دوربین (HAL) مدیریت می‌شود.

بسیاری از سازندگان تلفن برنامه‌های دوربین دست اول توسعه می‌دهند که معمولاً ازپیش روی دستگاه‌هایشان نصب می‌شود. برای استفاده از همه قابلیت‌های سخت‌افزار، ممکن است از APIهای خصوصی یا پنهان استفاده کنند یا از پیاده‌سازی درایور که برنامه‌های دیگر به آن دسترسی ندارند، رفتار ویژه‌ای دریافت کنند. برخی‌از دستگاه‌ها مفهوم دوربین‌های منطقی را با ارائه جاری‌سازی ترکیب‌شده از قاب‌های دوربین‌های فیزیکی مختلف پیاده‌سازی می‌کنند، اما فقط برای برنامه‌های ممتاز خاص. اغلب، فقط یکی از دوربین‌های فیزیکی درمعرض چارچوب قرار می‌گیرد. وضعیت توسعه‌دهندگان طرف سوم قبل‌از Android 9 در نمودار زیر نشان داده شده است:

شکل ۱. قابلیت‌های دوربین معمولاً فقط برای برنامه‌های ممتاز دردسترس است

از Android 9 به بعد، دیگر استفاده از «میاناهای برنامه‌سازی کاربردی خصوصی» در برنامه‌های Android مجاز نیست. با افزودن پشتیبانی از چند دوربین در چارچوب، رویه‌های پیشنهادی Android به سازندگان تلفن توصیه می‌کند که برای همه دوربین‌های فیزیکی که به یک جهت نگاه می‌کنند، دوربین منطقی ایجاد کنند. توسعه‌دهندگان طرف سوم باید انتظار داشته باشند که موارد زیر را در دستگاه‌های دارای Android 9 و بالاتر ببینند:

شکل ۲. دسترسی کامل توسعه‌دهنده به همه دستگاه‌های دوربین از Android 9 شروع می‌شود

آنچه دوربین منطقی ارائه می‌دهد کاملاً به پیاده‌سازی OEM از Camera HAL بستگی دارد. برای مثال، دستگاهی مثل Pixel 3 دوربین منطقی خود را به‌گونه‌ای پیاده‌سازی می‌کند که یکی از دوربین‌های فیزیکی‌اش را براساس فاصله کانونی و ناحیه برش درخواستی انتخاب می‌کند.

میانای برنامه‌سازی کاربردی چنددوربینی

میانای برنامه‌سازی کاربردی جدید ثابت‌ها، کلاس‌ها، و روش‌های جدید زیر را اضافه می‌کند:

به‌دلیل تغییرات «سند تعریف سازگاری Android» (CDD)، میانای برنامه‌سازی کاربردی چنددوربینی نیز انتظارات خاصی از توسعه‌دهندگان دارد. دستگاه‌های دارای دوربین دوتایی قبل‌از Android 9 وجود داشتند، اما باز کردن هم‌زمان بیش از یک دوربین شامل آزمون و خطا می‌شد. در Android 9 و بالاتر، چنددوربینی مجموعه‌ای از قوانین را برای تعیین زمان امکان‌پذیر بودن باز کردن جفت دوربین فیزیکی که بخشی از یک دوربین منطقی هستند ارائه می‌دهد.

در اکثر موارد، دستگاه‌های دارای Android 9 و بالاتر همه دوربین‌های فیزیکی (به‌جز احتمالاً انواع حسگرهای کمتر رایج مثل فروسرخ) را به‌همراه دوربین منطقی با کاربری آسان‌تر درمعرض دید قرار می‌دهند. برای هر ترکیب جاری‌سازی که تضمین شده است کار کند، یک جاری‌سازی متعلق به دوربین منطقی می‌تواند با دو جاری‌سازی از دوربین‌های فیزیکی زیرین جایگزین شود.

چندین جاری‌سازی به‌طور هم‌زمان

استفاده از چند جاری‌سازی دوربین به‌طور هم‌زمان قوانین استفاده از چند جاری‌سازی به‌طور هم‌زمان در یک دوربین را پوشش می‌دهد. با یک افزودنی قابل‌توجه، همان قوانین برای چند دوربین اعمال می‌شود. CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERAنحوه جایگزین کردن جاری‌سازی منطقی YUV_420_888 یا جاری‌سازی خام با دو جاری‌سازی فیزیکی را توضیح می‌دهد. یعنی هر جاری‌سازی از نوع YUV یا RAW می‌تواند با دو جاری‌سازی از نوع و اندازه یکسان جایگزین شود. می‌توانید با جاری‌سازی دوربین با پیکربندی تضمین‌شده زیر برای دستگاه‌های تک‌دوربینی شروع کنید:

  • جاری‌سازی ۱: نوع YUV، اندازه MAXIMUM از دوربین منطقی id = 0

سپس، دستگاهی با پشتیبانی از چند دوربین به شما امکان می‌دهد جلسه‌ای ایجاد کنید که آن جاری‌سازی منطقی YUV را با دو جاری‌سازی فیزیکی جایگزین می‌کند:

  • جاری‌سازی ۱: نوع YUV، اندازه MAXIMUM از دوربین فیزیکی id = 1
  • جاری‌سازی ۲: نوع YUV، MAXIMUM اندازه از دوربین فیزیکی id = 2

فقط درصورتی می‌توانید جاری‌سازی YUV یا RAW را با دو جاری‌سازی معادل جایگزین کنید که آن دو دوربین بخشی از گروه‌بندی دوربین منطقی باشند، که در CameraCharacteristics.getPhysicalCameraIds() فهرست شده است.

ضمانت‌های ارائه‌شده توسط چارچوب فقط حداقل‌های لازم برای دریافت هم‌زمان قاب از بیش از یک دوربین فیزیکی است. جریان‌های اضافی در اکثر دستگاه‌ها پشتیبانی می‌شوند، گاهی حتی امکان باز کردن چندین دستگاه دوربین فیزیکی به‌صورت مستقل را فراهم می‌کنند. ازآنجایی‌که چارچوب تضمین محکمی برای این کار ارائه نمی‌دهد، انجام آن مستلزم انجام آزمایش و تنظیم برای هر دستگاه بااستفاده از آزمون و خطا است.

ایجاد جلسه با چند دوربین فیزیکی

هنگام استفاده از دوربین‌های فیزیکی در دستگاهی که از چند دوربین پشتیبانی می‌کند، یک CameraDevice (دوربین منطقی) را باز کنید و در یک جلسه با آن تعامل داشته باشید. جلسه واحد را بااستفاده از API CameraDevice.createCaptureSession(SessionConfiguration config) ایجاد کنید که در سطح API 28 اضافه شده است. پیکربندی جلسه دارای تعدادی پیکربندی برونداد است که هریک مجموعه‌ای از هدف‌های برونداد و، به‌صورت اختیاری، شناسه دوربین فیزیکی موردنظر را دارند.

شکل ۳. مدل SessionConfiguration و OutputConfiguration

درخواست‌های ضبط دارای هدف خروجی مرتبط با آن‌ها هستند. چارچوب براساس هدف خروجی متصل تعیین می‌کند که درخواست‌ها به کدام دوربین فیزیکی (یا منطقی) ارسال شود. اگر هدف برونداد با یکی از اهداف بروندادی که به‌عنوان پیکربندی برونداد همراه با شناسه دوربین فیزیکی ارسال شده است مطابقت داشته باشد، آن دوربین فیزیکی درخواست را دریافت و پردازش می‌کند.

استفاده از یک جفت دوربین فیزیکی

یکی دیگر از موارد اضافه شده به APIهای دوربین برای چنددوربینی، امکان شناسایی دوربین‌های منطقی و یافتن دوربین‌های فیزیکی پشت آن‌ها است. می‌توانید تابعی تعریف کنید که به شناسایی جفت‌های احتمالی دوربین‌های فیزیکی که می‌توانید برای جایگزینی یکی از جاری‌سازی‌های دوربین منطقی استفاده کنید کمک کند:

کاتلین

/**
     * Helper class used to encapsulate a logical camera and two underlying
     * physical cameras
     */
    data class DualCamera(val logicalId: String, val physicalId1: String, val physicalId2: String)

    fun findDualCameras(manager: CameraManager, facing: Int? = null): List {
        val dualCameras = MutableList()

        // Iterate over all the available camera characteristics
        manager.cameraIdList.map {
            Pair(manager.getCameraCharacteristics(it), it)
        }.filter {
            // Filter by cameras facing the requested direction
            facing == null || it.first.get(CameraCharacteristics.LENS_FACING) == facing
        }.filter {
            // Filter by logical cameras
            // CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA requires API >= 28
            it.first.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES)!!.contains(
                CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA)
        }.forEach {
            // All possible pairs from the list of physical cameras are valid results
            // NOTE: There could be N physical cameras as part of a logical camera grouping
            // getPhysicalCameraIds() requires API >= 28
            val physicalCameras = it.first.physicalCameraIds.toTypedArray()
            for (idx1 in 0 until physicalCameras.size) {
                for (idx2 in (idx1 + 1) until physicalCameras.size) {
                    dualCameras.add(DualCamera(
                        it.second, physicalCameras[idx1], physicalCameras[idx2]))
                }
            }
        }

        return dualCameras
    }

جاوا

/**
     * Helper class used to encapsulate a logical camera and two underlying
     * physical cameras
     */
    final class DualCamera {
        final String logicalId;
        final String physicalId1;
        final String physicalId2;

        DualCamera(String logicalId, String physicalId1, String physicalId2) {
            this.logicalId = logicalId;
            this.physicalId1 = physicalId1;
            this.physicalId2 = physicalId2;
        }
    }
    List findDualCameras(CameraManager manager, Integer facing) {
        List dualCameras = new ArrayList<>();

        List cameraIdList;
        try {
            cameraIdList = Arrays.asList(manager.getCameraIdList());
        } catch (CameraAccessException e) {
            e.printStackTrace();
            cameraIdList = new ArrayList<>();
        }

        // Iterate over all the available camera characteristics
        cameraIdList.stream()
                .map(id -> {
                    try {
                        CameraCharacteristics characteristics = manager.getCameraCharacteristics(id);
                        return new Pair<>(characteristics, id);
                    } catch (CameraAccessException e) {
                        e.printStackTrace();
                        return null;
                    }
                })
                .filter(pair -> {
                    // Filter by cameras facing the requested direction
                    return (pair != null) &&
                            (facing == null || pair.first.get(CameraCharacteristics.LENS_FACING).equals(facing));
                })
                .filter(pair -> {
                    // Filter by logical cameras
                    // CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA requires API >= 28
                    IntPredicate logicalMultiCameraPred =
                            arg -> arg == CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA;
                    return Arrays.stream(pair.first.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES))
                            .anyMatch(logicalMultiCameraPred);
                })
                .forEach(pair -> {
                    // All possible pairs from the list of physical cameras are valid results
                    // NOTE: There could be N physical cameras as part of a logical camera grouping
                    // getPhysicalCameraIds() requires API >= 28
                    String[] physicalCameras = pair.first.getPhysicalCameraIds().toArray(new String[0]);
                    for (int idx1 = 0; idx1 < physicalCameras.length; idx1++) {
                        for (int idx2 = idx1 + 1; idx2 < physicalCameras.length; idx2++) {
                            dualCameras.add(
                                    new DualCamera(pair.second, physicalCameras[idx1], physicalCameras[idx2]));
                        }
                    }
                });
return dualCameras;
}

مدیریت وضعیت دوربین‌های فیزیکی توسط دوربین منطقی کنترل می‌شود. برای باز کردن «دوربین دوگانه»، دوربین منطقی مربوط به دوربین‌های فیزیکی را باز کنید:

کاتلین

fun openDualCamera(cameraManager: CameraManager,
                       dualCamera: DualCamera,
        // AsyncTask is deprecated beginning API 30
                       executor: Executor = AsyncTask.SERIAL_EXECUTOR,
                       callback: (CameraDevice) -> Unit) {

        // openCamera() requires API >= 28
        cameraManager.openCamera(
            dualCamera.logicalId, executor, object : CameraDevice.StateCallback() {
                override fun onOpened(device: CameraDevice) = callback(device)
                // Omitting for brevity...
                override fun onError(device: CameraDevice, error: Int) = onDisconnected(device)
                override fun onDisconnected(device: CameraDevice) = device.close()
            })
    }

جاوا

void openDualCamera(CameraManager cameraManager,
                        DualCamera dualCamera,
                        Executor executor,
                        CameraDeviceCallback cameraDeviceCallback
    ) {

        // openCamera() requires API >= 28
        cameraManager.openCamera(dualCamera.logicalId, executor, new CameraDevice.StateCallback() {
            @Override
            public void onOpened(@NonNull CameraDevice cameraDevice) {
               cameraDeviceCallback.callback(cameraDevice);
            }

            @Override
            public void onDisconnected(@NonNull CameraDevice cameraDevice) {
                cameraDevice.close();
            }

            @Override
            public void onError(@NonNull CameraDevice cameraDevice, int i) {
                onDisconnected(cameraDevice);
            }
        });
    }

به‌جز انتخاب دوربین برای باز کردن، فرایند همانند باز کردن دوربین در نسخه‌های قبلی Android است. ایجاد جلسه ضبط بااستفاده از میانای برنامه‌سازی کاربردی پیکربندی جلسه جدید به چارچوب می‌گوید که هدف‌های خاصی را با شناسه‌های دوربین فیزیکی خاصی مرتبط کند:

کاتلین

/**
 * Helper type definition that encapsulates 3 sets of output targets:
 *
 *   1. Logical camera
 *   2. First physical camera
 *   3. Second physical camera
 */
typealias DualCameraOutputs =
        Triple<MutableList?, MutableList?, MutableList?>

fun createDualCameraSession(cameraManager: CameraManager,
                            dualCamera: DualCamera,
                            targets: DualCameraOutputs,
                            // AsyncTask is deprecated beginning API 30
                            executor: Executor = AsyncTask.SERIAL_EXECUTOR,
                            callback: (CameraCaptureSession) -> Unit) {

    // Create 3 sets of output configurations: one for the logical camera, and
    // one for each of the physical cameras.
    val outputConfigsLogical = targets.first?.map { OutputConfiguration(it) }
    val outputConfigsPhysical1 = targets.second?.map {
        OutputConfiguration(it).apply { setPhysicalCameraId(dualCamera.physicalId1) } }
    val outputConfigsPhysical2 = targets.third?.map {
        OutputConfiguration(it).apply { setPhysicalCameraId(dualCamera.physicalId2) } }

    // Put all the output configurations into a single flat array
    val outputConfigsAll = arrayOf(
        outputConfigsLogical, outputConfigsPhysical1, outputConfigsPhysical2)
        .filterNotNull().flatMap { it }

    // Instantiate a session configuration that can be used to create a session
    val sessionConfiguration = SessionConfiguration(
        SessionConfiguration.SESSION_REGULAR,
        outputConfigsAll, executor, object : CameraCaptureSession.StateCallback() {
            override fun onConfigured(session: CameraCaptureSession) = callback(session)
            // Omitting for brevity...
            override fun onConfigureFailed(session: CameraCaptureSession) = session.device.close()
        })

    // Open the logical camera using the previously defined function
    openDualCamera(cameraManager, dualCamera, executor = executor) {

        // Finally create the session and return via callback
        it.createCaptureSession(sessionConfiguration)
    }
}

جاوا

/**
 * Helper class definition that encapsulates 3 sets of output targets:
 * 

* 1. Logical camera * 2. First physical camera * 3. Second physical camera */ final class DualCameraOutputs { private final List logicalCamera; private final List firstPhysicalCamera; private final List secondPhysicalCamera; public DualCameraOutputs(List logicalCamera, List firstPhysicalCamera, List third) { this.logicalCamera = logicalCamera; this.firstPhysicalCamera = firstPhysicalCamera; this.secondPhysicalCamera = third; } public List getLogicalCamera() { return logicalCamera; } public List getFirstPhysicalCamera() { return firstPhysicalCamera; } public List getSecondPhysicalCamera() { return secondPhysicalCamera; } } interface CameraCaptureSessionCallback { void callback(CameraCaptureSession cameraCaptureSession); } void createDualCameraSession(CameraManager cameraManager, DualCamera dualCamera, DualCameraOutputs targets, Executor executor, CameraCaptureSessionCallback cameraCaptureSessionCallback) { // Create 3 sets of output configurations: one for the logical camera, and // one for each of the physical cameras. List outputConfigsLogical = targets.getLogicalCamera().stream() .map(OutputConfiguration::new) .collect(Collectors.toList()); List outputConfigsPhysical1 = targets.getFirstPhysicalCamera().stream() .map(s -> { OutputConfiguration outputConfiguration = new OutputConfiguration(s); outputConfiguration.setPhysicalCameraId(dualCamera.physicalId1); return outputConfiguration; }) .collect(Collectors.toList()); List outputConfigsPhysical2 = targets.getSecondPhysicalCamera().stream() .map(s -> { OutputConfiguration outputConfiguration = new OutputConfiguration(s); outputConfiguration.setPhysicalCameraId(dualCamera.physicalId2); return outputConfiguration; }) .collect(Collectors.toList()); // Put all the output configurations into a single flat array List outputConfigsAll = Stream.of( outputConfigsLogical, outputConfigsPhysical1, outputConfigsPhysical2 ) .filter(Objects::nonNull) .flatMap(Collection::stream) .collect(Collectors.toList()); // Instantiate a session configuration that can be used to create a session SessionConfiguration sessionConfiguration = new SessionConfiguration( SessionConfiguration.SESSION_REGULAR, outputConfigsAll, executor, new CameraCaptureSession.StateCallback() { @Override public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) { cameraCaptureSessionCallback.callback(cameraCaptureSession); } // Omitting for brevity... @Override public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) { cameraCaptureSession.getDevice().close(); } }); // Open the logical camera using the previously defined function openDualCamera(cameraManager, dualCamera, executor, (CameraDevice c) -> // Finally create the session and return via callback c.createCaptureSession(sessionConfiguration)); }

برای اطلاعات درباره اینکه کدام ترکیب از جاری‌سازی‌ها پشتیبانی می‌شود، createCaptureSession را ببینید. ترکیب کردن جاری‌سازی‌ها برای چندین جاری‌سازی در یک دوربین منطقی است. سازگاری به استفاده از پیکربندی یکسان و جایگزینی یکی از آن جاری‌سازی‌ها با دو جاری‌سازی از دو دوربین فیزیکی که بخشی از دوربین منطقی یکسان هستند گسترش می‌یابد.

با آماده بودن جلسه دوربین، درخواست‌های ضبط موردنظر را اعزام کنید. هر هدف درخواست ضبط، داده‌هایش را از دوربین فیزیکی مرتبط خود دریافت می‌کند، اگر دوربینی درحال استفاده باشد، یا به دوربین منطقی برمی‌گردد.

مورد استفاده نمونه Zoom

می‌توان از ادغام دوربین‌های فیزیکی در یک جاری‌سازی استفاده کرد تا کاربران بتوانند بین دوربین‌های فیزیکی مختلف جابه‌جا شوند و میدان دید متفاوتی را تجربه کنند و درواقع «سطح زوم» متفاوتی را ضبط کنند.

شکل ۴. مثالی از تعویض دوربین‌ها برای مورد استفاده سطح بزرگ‌نمایی (از «آگهی Pixel 3»)

با انتخاب جفت دوربین فیزیکی شروع کنید تا به کاربران اجازه دهید بین آن‌ها جابه‌جا شوند. برای حداکثر تأثیر، می‌توانید جفت دوربینی را انتخاب کنید که حداقل و حداکثر فاصله کانونی دردسترس را ارائه می‌دهد.

کاتلین

fun findShortLongCameraPair(manager: CameraManager, facing: Int? = null): DualCamera? {

    return findDualCameras(manager, facing).map {
        val characteristics1 = manager.getCameraCharacteristics(it.physicalId1)
        val characteristics2 = manager.getCameraCharacteristics(it.physicalId2)

        // Query the focal lengths advertised by each physical camera
        val focalLengths1 = characteristics1.get(
            CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS) ?: floatArrayOf(0F)
        val focalLengths2 = characteristics2.get(
            CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS) ?: floatArrayOf(0F)

        // Compute the largest difference between min and max focal lengths between cameras
        val focalLengthsDiff1 = focalLengths2.maxOrNull()!! - focalLengths1.minOrNull()!!
        val focalLengthsDiff2 = focalLengths1.maxOrNull()!! - focalLengths2.minOrNull()!!

        // Return the pair of camera IDs and the difference between min and max focal lengths
        if (focalLengthsDiff1 < focalLengthsDiff2) {
            Pair(DualCamera(it.logicalId, it.physicalId1, it.physicalId2), focalLengthsDiff1)
        } else {
            Pair(DualCamera(it.logicalId, it.physicalId2, it.physicalId1), focalLengthsDiff2)
        }

        // Return only the pair with the largest difference, or null if no pairs are found
    }.maxByOrNull { it.second }?.first
}

جاوا

// Utility functions to find min/max value in float[]
    float findMax(float[] array) {
        float max = Float.NEGATIVE_INFINITY;
        for(float cur: array)
            max = Math.max(max, cur);
        return max;
    }
    float findMin(float[] array) {
        float min = Float.NEGATIVE_INFINITY;
        for(float cur: array)
            min = Math.min(min, cur);
        return min;
    }

DualCamera findShortLongCameraPair(CameraManager manager, Integer facing) {
        return findDualCameras(manager, facing).stream()
                .map(c -> {
                    CameraCharacteristics characteristics1;
                    CameraCharacteristics characteristics2;
                    try {
                        characteristics1 = manager.getCameraCharacteristics(c.physicalId1);
                        characteristics2 = manager.getCameraCharacteristics(c.physicalId2);
                    } catch (CameraAccessException e) {
                        e.printStackTrace();
                        return null;
                    }

                    // Query the focal lengths advertised by each physical camera
                    float[] focalLengths1 = characteristics1.get(
                            CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS);
                    float[] focalLengths2 = characteristics2.get(
                            CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS);

                    // Compute the largest difference between min and max focal lengths between cameras
                    Float focalLengthsDiff1 = findMax(focalLengths2) - findMin(focalLengths1);
                    Float focalLengthsDiff2 = findMax(focalLengths1) - findMin(focalLengths2);

                    // Return the pair of camera IDs and the difference between min and max focal lengths
                    if (focalLengthsDiff1 < focalLengthsDiff2) {
                        return new Pair<>(new DualCamera(c.logicalId, c.physicalId1, c.physicalId2), focalLengthsDiff1);
                    } else {
                        return new Pair<>(new DualCamera(c.logicalId, c.physicalId2, c.physicalId1), focalLengthsDiff2);
                    }

                }) // Return only the pair with the largest difference, or null if no pairs are found
                .max(Comparator.comparing(pair -> pair.second)).get().first;
    }

معماری منطقی برای این کار داشتن دو SurfaceViews است—یکی برای هر جاری‌سازی. این SurfaceViews براساس تعامل کاربر جابه‌جا می‌شوند تا در هر زمان فقط یکی از آن‌ها قابل‌مشاهده باشد.

کد زیر نحوه باز کردن دوربین منطقی، پیکربندی بروندادهای دوربین، ایجاد جلسه دوربین، و شروع دو جاری‌سازی پیش‌نمایش را نشان می‌دهد:

کاتلین

val cameraManager: CameraManager = ...

// Get the two output targets from the activity / fragment
val surface1 = ...  // from SurfaceView
val surface2 = ...  // from SurfaceView

val dualCamera = findShortLongCameraPair(manager)!!
val outputTargets = DualCameraOutputs(
    null, mutableListOf(surface1), mutableListOf(surface2))

// Here you open the logical camera, configure the outputs and create a session
createDualCameraSession(manager, dualCamera, targets = outputTargets) { session ->

  // Create a single request which has one target for each physical camera
  // NOTE: Each target receive frames from only its associated physical camera
  val requestTemplate = CameraDevice.TEMPLATE_PREVIEW
  val captureRequest = session.device.createCaptureRequest(requestTemplate).apply {
    arrayOf(surface1, surface2).forEach { addTarget(it) }
  }.build()

  // Set the sticky request for the session and you are done
  session.setRepeatingRequest(captureRequest, null, null)
}

جاوا

CameraManager manager = ...;

        // Get the two output targets from the activity / fragment
        Surface surface1 = ...;  // from SurfaceView
        Surface surface2 = ...;  // from SurfaceView

        DualCamera dualCamera = findShortLongCameraPair(manager, null);
                DualCameraOutputs outputTargets = new DualCameraOutputs(
                null, Collections.singletonList(surface1), Collections.singletonList(surface2));

        // Here you open the logical camera, configure the outputs and create a session
        createDualCameraSession(manager, dualCamera, outputTargets, null, (session) -> {
            // Create a single request which has one target for each physical camera
            // NOTE: Each target receive frames from only its associated physical camera
            CaptureRequest.Builder captureRequestBuilder;
            try {
                captureRequestBuilder = session.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
                Arrays.asList(surface1, surface2).forEach(captureRequestBuilder::addTarget);

                // Set the sticky request for the session and you are done
                session.setRepeatingRequest(captureRequestBuilder.build(), null, null);
            } catch (CameraAccessException e) {
                e.printStackTrace();
            }
        });

تنها کاری که باید انجام دهید این است که یک واسط کاربر برای کاربر فراهم کنید تا بین دو سطح جابه‌جا شود، مثلاً دکمه یا دوضربه زدن روی SurfaceView. حتی می‌توانید نوعی تحلیل صحنه انجام دهید و به‌طور خودکار بین دو جاری‌سازی جابه‌جا شوید.

اعوجاج لنز

همه لنزها مقدار مشخصی اعوجاج ایجاد می‌کنند. در Android، می‌توانید بااستفاده از CameraCharacteristics.LENS_DISTORTION، که جایگزین CameraCharacteristics.LENS_RADIAL_DISTORTION منسوخ‌شده است، انحراف ایجادشده توسط لنزها را پُرسمان کنید. برای دوربین‌های منطقی، اعوجاج حداقل است و برنامه شما می‌تواند از قاب‌ها کم‌وبیش همان‌گونه که از دوربین می‌آیند استفاده کند. برای دوربین‌های فیزیکی، به‌ویژه در لنزهای عریض، پیکربندی‌های لنز بالقوه بسیار متفاوتی وجود دارد.

برخی‌از دستگاه‌ها ممکن است تصحیح خودکار اعوجاج را ازطریق CaptureRequest.DISTORTION_CORRECTION_MODE پیاده‌سازی کنند. تصحیح اعوجاج به‌طور پیش‌فرض برای اکثر دستگاه‌ها روشن است.

کاتلین

val cameraSession: CameraCaptureSession = ...

        // Use still capture template to build the capture request
        val captureRequest = cameraSession.device.createCaptureRequest(
            CameraDevice.TEMPLATE_STILL_CAPTURE
        )

        // Determine if this device supports distortion correction
        val characteristics: CameraCharacteristics = ...
        val supportsDistortionCorrection = characteristics.get(
            CameraCharacteristics.DISTORTION_CORRECTION_AVAILABLE_MODES
        )?.contains(
            CameraMetadata.DISTORTION_CORRECTION_MODE_HIGH_QUALITY
        ) ?: false

        if (supportsDistortionCorrection) {
            captureRequest.set(
                CaptureRequest.DISTORTION_CORRECTION_MODE,
                CameraMetadata.DISTORTION_CORRECTION_MODE_HIGH_QUALITY
            )
        }

        // Add output target, set other capture request parameters...

        // Dispatch the capture request
        cameraSession.capture(captureRequest.build(), ...)

جاوا

CameraCaptureSession cameraSession = ...;

        // Use still capture template to build the capture request
        CaptureRequest.Builder captureRequestBuilder = null;
        try {
            captureRequestBuilder = cameraSession.getDevice().createCaptureRequest(
                    CameraDevice.TEMPLATE_STILL_CAPTURE
            );
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }

        // Determine if this device supports distortion correction
        CameraCharacteristics characteristics = ...;
        boolean supportsDistortionCorrection = Arrays.stream(
                        characteristics.get(
                                CameraCharacteristics.DISTORTION_CORRECTION_AVAILABLE_MODES
                        ))
                .anyMatch(i -> i == CameraMetadata.DISTORTION_CORRECTION_MODE_HIGH_QUALITY);
        if (supportsDistortionCorrection) {
            captureRequestBuilder.set(
                    CaptureRequest.DISTORTION_CORRECTION_MODE,
                    CameraMetadata.DISTORTION_CORRECTION_MODE_HIGH_QUALITY
            );
        }

        // Add output target, set other capture request parameters...

        // Dispatch the capture request
        cameraSession.capture(captureRequestBuilder.build(), ...);

تنظیم درخواست ضبط در این حالت می‌تواند بر نرخ فریم تولیدشده توسط دوربین تأثیر بگذارد. می‌توانید انتخاب کنید که تصحیح اعوجاج فقط برای تصاویر ثابت ضبط‌شده تنظیم شود.