หมายเหตุ: หน้านี้เกี่ยวข้องกับแพ็กเกจ camera2 เราขอแนะนำให้ใช้ cameraX เว้นแต่ว่าแอปของคุณต้องใช้ฟีเจอร์ระดับต่ำที่เฉพาะเจาะจงจาก Camera2 ทั้ง CameraX และ Camera2 รองรับ Android 5.0 (API ระดับ 21) ขึ้นไป
กล้องหลายตัวเปิดตัวใน Android 9 (API ระดับ 28) ตั้งแต่เปิดตัว ได้ออกสู่ตลาดที่รองรับ API แล้ว กรณีการใช้งานกล้องหลายตัว ทำงานคู่กับการกำหนดค่าฮาร์ดแวร์ที่เฉพาะเจาะจงอย่างเหนียวแน่น กล่าวคือ ไม่ใช่ กรณีการใช้งานทั้งหมดใช้ได้กับอุปกรณ์ทุกเครื่อง ซึ่งทำให้ฟีเจอร์กล้องหลายตัว เป็นตัวเลือกที่ดีสำหรับฟีเจอร์ Play การนำส่ง
กรณีการใช้งานทั่วไปมีดังนี้
- ซูม: สลับระหว่างกล้องต่างๆ โดยขึ้นอยู่กับพื้นที่ที่ครอบตัดหรือโฟกัสที่ต้องการ
- ความลึก: การใช้กล้องหลายตัวเพื่อสร้างแผนที่ที่มีความลึก
- โบเก้: ใช้ข้อมูลความลึกที่อนุมานเพื่อจำลองภาพมุมแคบเหมือนกล้อง DSLR ช่วงโฟกัส
ความแตกต่างระหว่างกล้องเชิงตรรกะและกล้องจริง
การทำความเข้าใจเกี่ยวกับ API กล้องหลายตัวจำเป็นต้องมีความเข้าใจในความแตกต่างระหว่าง กล้องตรรกะและกล้องจริง สำหรับการอ้างอิง ให้ลองพิจารณาอุปกรณ์ที่มี กล้องหลัง ในตัวอย่างนี้ กล้องหลัง 3 ตัว เป็นกล้องจริง กล้องเชิงตรรกะคือ การจัดกลุ่ม 2 กลุ่มขึ้นไป ของกล้องตัวอื่นๆ เอาต์พุตของตรรกะ กล้องอาจเป็นสตรีม ที่มาจากกล้องจริงตัวใดตัวหนึ่ง หรือสตรีมรวมที่มาจากกล้องจริงมากกว่า 1 ตัว พร้อมกัน ไม่ว่าจะเลือกวิธีใด ฮาร์ดแวร์กล้องจะเป็นผู้จัดการสตรีม Abstraction Layer (HAL)
ผู้ผลิตโทรศัพท์หลายรายพัฒนาแอปพลิเคชันกล้องบุคคลที่หนึ่ง ซึ่งโดยปกติแล้ว ติดตั้งมาล่วงหน้าบนอุปกรณ์ของตน หากต้องการใช้ความสามารถทั้งหมดของฮาร์ดแวร์ อาจใช้ API ส่วนตัวหรือ API ที่ซ่อนไว้ หรือได้รับการปฏิบัติเป็นพิเศษจาก การใช้งานไดรเวอร์ที่แอปพลิเคชันอื่นๆ ไม่มีสิทธิ์เข้าถึง ใช้บ้าง จะใช้แนวคิดเรื่องกล้องลอจิคัลโดยมอบสตรีมการผสมผสานระหว่าง เฟรมต่างๆ จากกล้องที่ติดมากับกล้องที่ต่างกัน หรือเป็นสิทธิ์เฉพาะบางอย่าง แอปพลิเคชัน บ่อยครั้งที่กล้องเพียงตัวใดตัวหนึ่งได้สัมผัสกับ สถานการณ์สำหรับนักพัฒนาซอฟต์แวร์บุคคลที่สามก่อน Android 9 คือ ที่แสดงในแผนภาพต่อไปนี้
ตั้งแต่ Android 9 เป็นต้นไป ระบบจะไม่อนุญาตให้ใช้ API ส่วนตัวในแอป Android อีกต่อไป ด้วยการรวมการสนับสนุนกล้องหลายตัวไว้ในเฟรมเวิร์ก Android ควรให้ผู้ผลิตโทรศัพท์ใช้กล้องเชิงตรรกะ สำหรับกล้องทุกตัวที่หันไปในทิศทางเดียวกัน ต่อไปนี้คือสิ่งที่ นักพัฒนาซอฟต์แวร์บุคคลที่สามควรคาดหวังว่าจะได้เห็นในอุปกรณ์ที่ใช้ Android 9 และ สูงกว่า:
สิ่งที่กล้องเชิงตรรกะมอบให้จะขึ้นอยู่กับการใช้งาน OEM ทั้งหมด ของ HAL ของกล้อง เช่น อุปกรณ์อย่าง Pixel 3 นำการใช้งานเชิงตรรกะมาใช้ ในลักษณะที่จะเลือกกล้องจริงตัวใดตัวหนึ่งจาก ความยาวโฟกัสและบริเวณที่ครอบตัดที่ขอ
API กล้องหลายตัว
โดย API ใหม่นี้จะเพิ่มค่าคงที่ คลาส และเมธอดใหม่ๆ ต่อไปนี้
CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA
CameraCharacteristics.getPhysicalCameraIds()
CameraCharacteristics.getAvailablePhysicalCameraRequestKeys()
CameraDevice.createCaptureSession(SessionConfiguration config)
CameraCharacteritics.LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE
OutputConfiguration
และSessionConfiguration
เนื่องจากมีการเปลี่ยนแปลงเอกสารคำจำกัดความความเข้ากันได้ของ Android (CDD) นอกจากนี้ API กล้องหลายตัวยังมาพร้อมกับความคาดหวังบางอย่างจากนักพัฒนาซอฟต์แวร์ อุปกรณ์ ที่มีกล้องคู่มาก่อน Android 9 แต่การเปิดกล้องมากกว่า 1 ตัว เป็นการลองผิดลองถูก ใน Android 9 ขึ้นไป กล้องหลายตัว ให้ชุดกฎที่ระบุว่าคุณสามารถเปิด ที่เป็นส่วนหนึ่งของกล้องแบบลอจิคัลเดียวกัน
ในกรณีส่วนใหญ่ อุปกรณ์ที่ใช้ Android 9 ขึ้นไปจะเผยให้เห็นถึง กล้อง (ยกเว้นสำหรับเซ็นเซอร์ประเภทที่ไม่ค่อยเป็นที่นิยม เช่น อินฟราเรด) กล้องเชิงตรรกะที่ใช้งานง่าย สำหรับชุดสตรีมที่ รับประกันได้ว่าจะใช้งานได้ สตรีมหนึ่งที่เป็นของกล้องเชิงตรรกะสามารถแทนที่ได้โดย 2 สตรีมจากกล้องจริงที่อยู่ด้านล่าง
สตรีมหลายรายการพร้อมกัน
การใช้สตรีมจากกล้องหลายตัวพร้อมกัน
ครอบคลุมกฎในการใช้สตรีมหลายรายการพร้อมกันในกล้องตัวเดียว
ด้วยการเพิ่มที่สำคัญ 1 รายการ กฎเดียวกันจะใช้กับกล้องหลายตัว
CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA
อธิบายวิธีแทนที่ YUV_420_888 หรือสตรีมดิบเชิงตรรกะด้วย
อุปกรณ์ต่างๆ กล่าวคือ สตรีม YUV หรือ RAW แต่ละประเภทสามารถแทนที่ด้วย
สตรีมสองสตรีมที่มีประเภทและขนาดเหมือนกัน เริ่มจากสตรีมกล้องของ
การกำหนดค่าที่รับประกันการแสดงผลสำหรับอุปกรณ์กล้องเดียวมีดังนี้
- สตรีม 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 การกำหนดค่าเซสชันมีเอาต์พุตจำนวนหนึ่ง
การกำหนดค่า โดยแต่ละแบบจะมีชุดของเป้าหมายเอาต์พุต และอาจมี
รหัสกล้องจริงที่ต้องการ
คำขอการบันทึกมีเป้าหมายเอาต์พุตเชื่อมโยงอยู่ เฟรมเวิร์ก กำหนดกล้องจริง (หรือกล้องที่ถูกต้อง) ที่ระบบจะส่งคำขอ สิ่งที่แนบกับเป้าหมายเอาต์พุต หากเป้าหมายเอาต์พุตสอดคล้องกับหนึ่งใน เป้าหมายเอาต์พุตที่ส่งเป็นการกำหนดค่าเอาต์พุตพร้อมกับเอาต์พุตจริง กล้อง กล้องตัวดังกล่าวจะได้รับและดำเนินการตามคำขอ
การใช้กล้องจริง 2 ตัว
อีกหนึ่งสิ่งที่เพิ่มเข้ามาจาก API กล้องถ่ายรูปสำหรับกล้องหลายตัวคือความสามารถในการระบุ กล้องตรรกะและค้นหากล้องจริงที่อยู่ด้านหลัง คุณสามารถกำหนด เพื่อช่วยระบุกล้องจริงที่อาจใช้ได้ เพื่อเปลี่ยนสตรีมจากกล้องลอจิคัล:
Kotlin
/** * 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 }
Java
/** * 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; } } ListfindDualCameras(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; }
การจัดการสถานะของกล้องจริงจะควบคุมด้วยกล้องตรรกะ ถึง เปิด "กล้องคู่" ให้เปิดกล้องเชิงตรรกะที่สอดคล้องกับ กล้อง:
Kotlin
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() }) }
Java
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 เวอร์ชันก่อนหน้านี้ การสร้างเซสชันการจับภาพโดยใช้ การกำหนดค่าเซสชัน API จะบอกเฟรมเวิร์กให้เชื่อมโยงเป้าหมายบางอย่างกับ รหัสกล้องจริงที่เฉพาะเจาะจง:
Kotlin
/** * 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 ?> 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) } }
Java
/** * 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
เพื่อดูข้อมูลว่ารองรับชุดสตรีมใด การรวมสตรีม
มีไว้สำหรับสตรีมหลายรายการในกล้องแบบลอจิคัลเดียว ความสามารถในการใช้งานร่วมกันรวมถึง
โดยใช้การกำหนดค่าเดียวกันและแทนที่สตรีมใดสตรีมหนึ่งด้วย 2 สตรีม
จากกล้องจริง 2 ตัวที่อยู่ในกล้องตรรกะเดียวกัน
ด้วยฟังก์ชัน เซสชันกล้อง พร้อมแล้ว ส่ง การเก็บข้อมูล ชิ้น เป้าหมายของคำขอบันทึกได้รับข้อมูลของตนจากทางกายภาพที่เกี่ยวข้อง กล้องถ่ายรูป หากมีการใช้งานอยู่ หรือถอยกลับไปใช้กล้องเชิงตรรกะ
ตัวอย่างกรณีการใช้งานของ Zoom
คุณสามารถใช้การรวมกล้องจริงไว้ในสตรีมเดียว ที่ผู้ใช้สามารถสลับไปมาระหว่างกล้องจริงแบบต่างๆ เพื่อสัมผัสประสบการณ์ ขอบเขตการมองเห็นที่แตกต่างกัน โดยจับภาพ "ระดับการซูม" ที่แตกต่างกันได้อย่างมีประสิทธิภาพ
เริ่มต้นด้วยการเลือกกล้องจริง 2 ตัวเพื่ออนุญาตให้ผู้ใช้เปลี่ยน ระหว่าง คุณสามารถเลือกกล้องคู่ที่ให้ผลลัพธ์สูงสุด ความยาวโฟกัสต่ำสุดและสูงสุดที่มี
Kotlin
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 }
Java
// 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
- 1 รายการต่อสตรีม
ระบบจะสลับ SurfaceViews
เหล่านี้ตามการโต้ตอบของผู้ใช้เพื่อให้มีเพียงรายการเดียวเท่านั้น
ที่มองเห็นได้ ณ เวลาใดเวลาหนึ่ง
โค้ดต่อไปนี้แสดงวิธีเปิดกล้องตรรกะ กำหนดค่ากล้อง สร้างเซสชันกล้อง และเริ่มสตรีมตัวอย่าง 2 รายการ ดังนี้
Kotlin
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) }
Java
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(); } });
ที่เหลือก็แค่จัดเตรียม UI เพื่อให้ผู้ใช้สลับระหว่าง
เช่น ปุ่มหรือการแตะสองครั้งที่ SurfaceView
คุณสามารถ
วิเคราะห์ฉากในรูปแบบใดรูปแบบหนึ่งและสลับระหว่างสตรีมทั้งสอง
โดยอัตโนมัติ
การบิดเบี้ยวของเลนส์
เลนส์ทุกตัวทำให้เกิดการบิดเบี้ยวในปริมาณหนึ่ง ใน Android คุณสามารถค้นหา
การบิดเบี้ยวที่เกิดจากเลนส์โดยใช้
CameraCharacteristics.LENS_DISTORTION
โดยจะแทนที่แท็ก
CameraCharacteristics.LENS_RADIAL_DISTORTION
สําหรับกล้องแบบลอจิคัล ความผิดเพี้ยนจะน้อยและแอปพลิเคชันของคุณสามารถใช้
จะได้เฟรมมากขึ้นหรือน้อยลง
เพราะมาจากกล้อง สำหรับกล้องจริง
อาจมีการกำหนดค่าเลนส์แตกต่างกันมาก โดยเฉพาะสำหรับเลนส์มุมกว้าง
เลนส์
อุปกรณ์บางเครื่องอาจใช้การแก้ไขการบิดเบี้ยวอัตโนมัติผ่าน
CaptureRequest.DISTORTION_CORRECTION_MODE
การแก้ไขการผิดเพี้ยนจะเปิดอยู่โดยค่าเริ่มต้นในอุปกรณ์ส่วนใหญ่
Kotlin
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(), ...)
Java
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(), ...);
การตั้งค่าคำขอบันทึกในโหมดนี้อาจส่งผลต่ออัตราเฟรมที่ ที่ได้จากกล้อง คุณอาจเลือกที่จะตั้งค่าการแก้ไขการบิดเบี้ยว การจับภาพนิ่ง