API چند دوربینی

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

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

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

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

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

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

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

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

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

شکل 2. دسترسی کامل توسعه دهندگان به تمام دستگاه های دوربین که در اندروید 9 شروع می شود

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

API چند دوربینی

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

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

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

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

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

  • جریان 1: نوع YUV، MAXIMUM اندازه از id = 0

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

  • جریان 1: نوع YUV، MAXIMUM اندازه از id = 1
  • جریان 2: نوع YUV، MAXIMUM اندازه از id = 2

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

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

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

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

شکل 3. مدل 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);
            }
        });
    }

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

کاتلین

/**
 * 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 مراجعه کنید. ترکیب استریم ها برای استریم های متعدد در یک دوربین منطقی است. این سازگاری به استفاده از پیکربندی یکسان و جایگزینی یکی از آن جریان ها با دو جریان از دو دوربین فیزیکی که بخشی از یک دوربین منطقی هستند گسترش می یابد.

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

مثال استفاده از زوم

می توان از ادغام دوربین های فیزیکی در یک جریان واحد استفاده کرد تا کاربران بتوانند بین دوربین های فیزیکی مختلف جابجا شوند تا میدان دید متفاوتی را تجربه کنند و به طور موثر "سطح بزرگنمایی" متفاوتی را ثبت کنند.

شکل 4. نمونه ای از تعویض دوربین ها برای سطح زوم مورد استفاده (از Pixel 3 Ad)

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

کاتلین

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 منسوخ شده است.LENS_RADIAL_DISTORTION، اعوجاج ایجاد شده توسط لنزها را جستجو کنید. برای دوربین های منطقی، اعوجاج حداقل است و برنامه شما می تواند از فریم ها کم و بیش همانطور که از دوربین می آیند استفاده کند. برای دوربین های فیزیکی، پیکربندی لنزهای بالقوه بسیار متفاوتی وجود دارد، به خصوص در لنزهای واید.

برخی از دستگاه‌ها ممکن است تصحیح اعوجاج خودکار را از طریق CaptureRequest.DISTORTION_CORRECTION_MODE اجرا کنند.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(), ...);

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