API กล้องหลายตัว

หมายเหตุ: หน้านี้อ้างอิงถึงแพ็กเกจ Camera2 เราขอแนะนำให้ใช้ CameraX เว้นแต่ว่าแอปของคุณต้องใช้ฟีเจอร์ระดับต่ำที่เฉพาะเจาะจงจาก Camera2 ทั้ง CameraX และ Camera2 รองรับ Android 5.0 (API ระดับ 21) ขึ้นไป

กล้องหลายตัวเปิดตัวพร้อมกับ Android 9 (API ระดับ 28) นับตั้งแต่เปิดตัว มีอุปกรณ์ที่รองรับ API นี้ออกสู่ตลาด กรณีการใช้งานกล้องหลายตัวจำนวนมาก เชื่อมต่ออย่างแน่นหนากับการกำหนดค่าฮาร์ดแวร์ที่เฉพาะเจาะจง กล่าวคือ กรณีการใช้งานบางอย่างอาจใช้ร่วมกับอุปกรณ์บางเครื่องไม่ได้ ซึ่งทำให้ฟีเจอร์กล้องหลายตัวเหมาะที่จะใช้การนำส่งฟีเจอร์ Play

กรณีการใช้งานทั่วไปมีดังนี้

  • ซูม: สลับไปมาระหว่างกล้องโดยขึ้นอยู่กับขอบเขตการครอบตัดหรือความยาวโฟกัสที่เลือก
  • ความลึก: ใช้กล้องหลายตัวเพื่อสร้างแผนที่ความลึก
  • โบเก้: ใช้ข้อมูลความลึกที่อนุมานเพื่อจำลองช่วงโฟกัสแคบๆ แบบกล้อง DSLR

ความแตกต่างระหว่างกล้องเชิงตรรกะและกล้องจริง

การทำความเข้าใจ API แบบหลายกล้องจำเป็นต้องเข้าใจความแตกต่างระหว่างกล้องแบบตรรกะและกล้องจริง ตัวอย่างเช่น ลองพิจารณาอุปกรณ์ที่มีกล้องหลัง 3 ตัว ในตัวอย่างนี้ กล้องหลังทั้ง 3 ตัวจะถือว่าเป็นกล้องจริง กล้องเชิงตรรกะคือการจัดกลุ่มกล้องจริงตั้งแต่ 2 ตัวขึ้นไป เอาต์พุตของกล้องเชิงตรรกะอาจเป็นสตรีมที่มาจากกล้องจริงตัวใดตัวหนึ่งที่เกี่ยวข้อง หรือสตรีมแบบรวมที่มาจากกล้องจริงที่เกี่ยวข้องมากกว่า 1 ตัวพร้อมกัน ไม่ว่าจะด้วยวิธีใด สตรีมจะได้รับการจัดการโดยระดับชั้นการจัดการฮาร์ดแวร์โดยตรง (HAL) ของกล้อง

ผู้ผลิตโทรศัพท์หลายรายพัฒนาแอปพลิเคชันกล้องของบุคคลที่หนึ่ง ซึ่งมักจะติดตั้งไว้ล่วงหน้าในอุปกรณ์ของตน โดยอาจใช้ API ส่วนตัวหรือ API ที่ซ่อนอยู่ หรือได้รับการดูแลเป็นพิเศษจากการติดตั้งใช้งานไดรเวอร์ที่แอปพลิเคชันอื่นๆ ไม่มีสิทธิ์เข้าถึง เพื่อใช้ความสามารถทั้งหมดของฮาร์ดแวร์ อุปกรณ์บางเครื่องใช้แนวคิดของกล้องเชิงตรรกะโดยการจัดเตรียมสตรีมเฟรมที่รวมจากกล้องจริงที่แตกต่างกัน แต่จะใช้ได้กับแอปพลิเคชันที่มีสิทธิ์บางแอปพลิเคชันเท่านั้น บ่อยครั้งที่เฟรมเวิร์กจะแสดงกล้องจริงเพียงตัวเดียว สถานการณ์สำหรับนักพัฒนาแอปบุคคลที่สามก่อน Android 9 แสดงอยู่ในแผนภาพต่อไปนี้

ความสามารถของกล้องที่โดยปกติแล้วจะใช้ได้เฉพาะกับแอปพลิเคชันที่มีสิทธิ์เท่านั้น
รูปที่ 1 ความสามารถของกล้องที่โดยปกติแล้วจะใช้ได้เฉพาะกับแอปพลิเคชันที่มีสิทธิ์พิเศษ

ตั้งแต่ Android 9 เป็นต้นไป ระบบจะไม่อนุญาตให้ใช้ API ส่วนตัวในแอป Android อีกต่อไป เมื่อมีการรองรับกล้องหลายตัวในเฟรมเวิร์ก แนวทางปฏิบัติแนะนำของ Android ขอแนะนำอย่างยิ่งให้ผู้ผลิตโทรศัพท์แสดงกล้องเชิงตรรกะสำหรับกล้องจริงทั้งหมดที่หันไปในทิศทางเดียวกัน ต่อไปนี้คือสิ่งที่นักพัฒนาแอปบุคคลที่สามควรคาดหวังว่าจะได้เห็นในอุปกรณ์ที่ใช้ Android 9 ขึ้นไป

นักพัฒนาแอปมีสิทธิ์เข้าถึงอุปกรณ์กล้องทั้งหมดอย่างเต็มรูปแบบตั้งแต่ Android 9 เป็นต้นไป
รูปที่ 2 นักพัฒนาแอปมีสิทธิ์เข้าถึงอุปกรณ์กล้องทั้งหมดอย่างเต็มรูปแบบตั้งแต่ Android 9 เป็นต้นไป

สิ่งที่กล้องเชิงตรรกะมีให้จะขึ้นอยู่กับการติดตั้งใช้งาน HAL ของกล้องโดย OEM ทั้งหมด ตัวอย่างเช่น อุปกรณ์อย่าง Pixel 3 ใช้กล้องเชิงตรรกะในลักษณะที่เลือกกล้องจริงตัวใดตัวหนึ่งโดยอิงตามความยาวโฟกัสและขอบเขตการครอบตัดที่ขอ

Multi-camera API

API ใหม่จะเพิ่มค่าคงที่ คลาส และเมธอดใหม่ต่อไปนี้

เนื่องจากการเปลี่ยนแปลงในเอกสารนิยามความเข้ากันได้ (CDD) ของ Android นักพัฒนาแอปจึงมีความคาดหวังบางอย่างเกี่ยวกับ Multi-Camera API ด้วย อุปกรณ์ที่มีกล้องคู่มีอยู่ก่อน Android 9 แต่การเปิดกล้องมากกว่า 1 ตัวพร้อมกันนั้นต้องอาศัยการลองผิดลองถูก ใน Android 9 ขึ้นไป กล้องหลายตัวจะมีชุดกฎเพื่อระบุว่าเมื่อใดที่สามารถเปิดกล้องจริงคู่หนึ่งซึ่งเป็นส่วนหนึ่งของกล้องเชิงตรรกะเดียวกันได้

ในกรณีส่วนใหญ่ อุปกรณ์ที่ใช้ Android 9 ขึ้นไปจะแสดงกล้องจริงทั้งหมด (ยกเว้นประเภทเซ็นเซอร์ที่พบได้น้อย เช่น อินฟราเรด) พร้อมกับกล้องเชิงตรรกะที่ใช้งานง่ายกว่า สำหรับสตรีมทุกชุดที่รับประกันว่าจะทำงานได้ สตรีม 1 รายการที่มาจากกล้องเชิงตรรกะจะแทนที่ด้วยสตรีม 2 รายการจากกล้องจริงที่เกี่ยวข้องได้

สตรีมหลายรายการพร้อมกัน

การใช้สตรีมจากกล้องหลายรายการพร้อมกันครอบคลุมกฎสำหรับการใช้สตรีมหลายรายการพร้อมกันในกล้องตัวเดียว กฎเดียวกันนี้ใช้กับกล้องหลายตัวได้ โดยมีข้อแตกต่างที่สำคัญเพียงข้อเดียว CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA อธิบายวิธีแทนที่สตรีม YUV_420_888 เชิงตรรกะหรือสตรีมข้อมูลดิบด้วยสตรีมจริง 2 รายการ กล่าวคือ สตรีมแต่ละรายการประเภท YUV หรือ RAW สามารถแทนที่ด้วยสตรีม 2 รายการที่มีประเภทและขนาดเหมือนกัน คุณสามารถเริ่มต้นด้วยสตรีมจากกล้องที่มีการกำหนดค่าที่รับประกันต่อไปนี้สำหรับอุปกรณ์ที่มีกล้องตัวเดียว

  • สตรีม 1: ประเภท YUV, ขนาด MAXIMUM จากกล้องเชิงตรรกะ id = 0

จากนั้นอุปกรณ์ที่รองรับกล้องหลายตัวจะช่วยให้คุณสร้างเซสชันที่แทนที่สตรีม YUV เชิงตรรกะนั้นด้วยสตรีมจริง 2 รายการได้ ดังนี้

  • สตรีม 1: ประเภท YUV, ขนาด MAXIMUM จากกล้องจริง id = 1
  • สตรีม 2: ประเภท YUV, ขนาด MAXIMUM จากกล้องจริง id = 2

คุณสามารถแทนที่สตรีม YUV หรือ RAW ด้วยสตรีมที่เทียบเท่า 2 รายการได้ก็ต่อเมื่อกล้อง 2 ตัวนั้นเป็นส่วนหนึ่งของการจัดกลุ่มกล้องเชิงตรรกะ ซึ่งแสดงอยู่ใน CameraCharacteristics.getPhysicalCameraIds()

การรับประกันที่เฟรมเวิร์กให้มาเป็นเพียงข้อกำหนดขั้นต่ำที่จำเป็นในการรับเฟรมจากกล้องจริงมากกว่า 1 ตัวพร้อมกัน อุปกรณ์ส่วนใหญ่รองรับสตรีมเพิ่มเติม และบางครั้งยังอนุญาตให้เปิดอุปกรณ์กล้องจริงหลายเครื่องแยกกันได้ด้วย เนื่องจากเฟรมเวิร์กไม่ได้ให้การรับประกันที่แน่นอน การดำเนินการดังกล่าวจึงต้องทำการทดสอบและปรับแต่งตามอุปกรณ์โดยใช้การลองผิดลองถูก

การสร้างเซสชันที่มีกล้องจริงหลายตัว

เมื่อใช้กล้องจริงในอุปกรณ์ที่เปิดใช้กล้องหลายตัว ให้เปิด CameraDevice (กล้องเชิงตรรกะ) เพียงตัวเดียวและโต้ตอบกับกล้องนั้นภายในเซสชันเดียว สร้างเซสชันเดียวโดยใช้ API CameraDevice.createCaptureSession(SessionConfiguration config) ซึ่งเพิ่มเข้ามาในระดับ API 28 การกำหนดค่าเซสชันมีการกำหนดค่าเอาต์พุตจำนวนหนึ่ง ซึ่งแต่ละรายการมีชุดเป้าหมายเอาต์พุต และรหัสกล้องจริงที่เลือก (ไม่บังคับ)

โมเดล SessionConfiguration และ OutputConfiguration
รูปที่ 3 โมเดล SessionConfiguration และ OutputConfiguration

คำขอการจับภาพมีเป้าหมายเอาต์พุตที่เชื่อมโยงอยู่ เฟรมเวิร์กจะกำหนดว่าจะส่งคำขอไปยังกล้องจริง (หรือกล้องเชิงตรรกะ) ตัวใดโดยอิงตามเป้าหมายเอาต์พุตที่แนบมา หากเป้าหมายเอาต์พุตสอดคล้องกับเป้าหมายเอาต์พุตรายการใดรายการหนึ่งที่ส่งเป็นการกำหนดค่าเอาต์พุตพร้อมกับรหัสกล้องจริง กล้องจริงนั้นจะรับและประมวลผลคำขอ

การใช้กล้องจริง 2 ตัว

สิ่งที่เพิ่มเข้ามาใน 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<DualCamera> {
    val dualCameras = mutableListOf<DualCamera>()

    // 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
        ) ?: false
    }.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
}

การจัดการสถานะของกล้องจริงจะควบคุมโดยกล้องเชิงตรรกะ หากต้องการเปิด "Dual Camera" ให้เปิดกล้องเชิงตรรกะที่สอดคล้องกับกล้องจริง ดังนี้

fun openDualCamera(
    cameraManager: CameraManager,
    dualCamera: DualCamera,
    executor: 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()
        }
    )
}

นอกเหนือจากการเลือกกล้องที่จะเปิดแล้ว กระบวนการนี้จะเหมือนกับการเปิดกล้องใน Android เวอร์ชันที่ผ่านมา การสร้างเซสชันการจับภาพโดยใช้ 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<Surface>?, MutableList<Surface>?, MutableList<Surface>?>

fun createDualCameraSession(
    cameraManager: CameraManager,
    dualCamera: DualCamera,
    targets: DualCameraOutputs,
    executor: 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 using the callback.
        it.createCaptureSession(sessionConfiguration)
    }
}

ดูข้อมูลเกี่ยวกับชุดสตรีมที่รองรับได้ที่ createCaptureSession การรวมสตรีมมีไว้สำหรับสตรีมหลายรายการในกล้องเชิงตรรกะเดียว ความเข้ากันได้นี้ครอบคลุมถึงการใช้การกำหนดค่าเดียวกันและการแทนที่สตรีมรายการใดรายการหนึ่งด้วยสตรีม 2 รายการจากกล้องจริง 2 ตัวที่เป็นส่วนหนึ่งของกล้องเชิงตรรกะเดียวกัน

เมื่อเซสชันกล้องพร้อมแล้ว ให้ส่งคำขอจับภาพที่เลือก เป้าหมายแต่ละรายการของคำขอจับภาพจะได้รับข้อมูลจากกล้องจริงที่เชื่อมโยง (หากมีการใช้งาน) หรือกลับไปใช้กล้องเชิงตรรกะ

ตัวอย่างกรณีการใช้งาน Zoom

คุณสามารถรวมกล้องจริงเข้าเป็นสตรีมเดียวเพื่อให้ผู้ใช้สลับระหว่างกล้องจริงต่างๆ เพื่อสัมผัสขอบเขตการมองเห็นที่แตกต่างกัน ซึ่งจะจับภาพ "ระดับการซูม" ที่แตกต่างกันได้อย่างมีประสิทธิภาพ

ตัวอย่างการสลับกล้องสำหรับกรณีการใช้งานระดับการซูม
รูปที่ 4 ตัวอย่างการสลับกล้องสำหรับกรณีการใช้งานระดับการซูม (จากโฆษณา 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() ?: 0f) - (focalLengths1.minOrNull() ?: 0f)
        val focalLengthsDiff2 = (focalLengths1.maxOrNull() ?: 0f) - (focalLengths2.minOrNull() ?: 0f)

        // 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
}

สถาปัตยกรรมที่เหมาะสมสำหรับกรณีนี้คือการมี SurfaceViews 2 รายการ โดยมี 1 รายการสำหรับแต่ละสตรีม SurfaceViewsเหล่านี้จะสลับกันตามการโต้ตอบของผู้ใช้เพื่อให้มองเห็นได้เพียงรายการเดียวในแต่ละครั้ง

โค้ดต่อไปนี้แสดงวิธีเปิดกล้องเชิงตรรกะ กำหนดค่าเอาต์พุตของกล้อง สร้างเซสชันกล้อง และเริ่มสตรีมตัวอย่าง 2 รายการ

val cameraManager: CameraManager = ...
val cameraExecutor: Executor = ...  // owned by your activity / fragment

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

val dualCamera = findShortLongCameraPair(cameraManager) ?: return
val outputTargets = DualCameraOutputs(
    null, mutableListOf(surface1), mutableListOf(surface2)
)

// Here you open the logical camera, configure the outputs and create a session.
createDualCameraSession(
    cameraManager, dualCamera, targets = outputTargets, executor = cameraExecutor
) { 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)
}

สิ่งเดียวที่เหลืออยู่คือการจัดเตรียม UI ให้ผู้ใช้สลับระหว่าง 2 พื้นผิว เช่น ปุ่มหรือการแตะ 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(), ...)

การตั้งค่าคำขอจับภาพในโหมดนี้อาจส่งผลต่ออัตราเฟรมที่กล้องสร้างได้ คุณอาจเลือกตั้งค่าการแก้ไขการบิดเบือนเฉพาะการจับภาพนิ่งเท่านั้น