หมายเหตุ: หน้านี้อ้างอิงถึงแพ็กเกจ Camera2 เราขอแนะนำให้ใช้ CameraX เว้นแต่แอปของคุณจะต้องใช้ฟีเจอร์ระดับต่ำที่เฉพาะเจาะจงจาก Camera2 ทั้ง CameraX และ Camera2 รองรับ Android 5.0 (API ระดับ 21) ขึ้นไป
แอปพลิเคชันกล้องสามารถใช้สตรีมเฟรมมากกว่า 1 รายการพร้อมกันได้ ในบางกรณี สตรีมที่แตกต่างกันอาจต้องใช้ความละเอียดของเฟรมหรือรูปแบบพิกเซลที่แตกต่างกันด้วย กรณีการใช้งานทั่วไปบางส่วนมีดังนี้
- การบันทึกวิดีโอ: สตรีมหนึ่งสำหรับการแสดงตัวอย่าง อีกสตรีมหนึ่งจะได้รับการเข้ารหัสและบันทึก ลงในไฟล์
- การสแกนบาร์โค้ด: สตรีมหนึ่งสำหรับแสดงตัวอย่าง อีกสตรีมหนึ่งสำหรับการตรวจหาบาร์โค้ด
- การถ่ายภาพคอมพิวเตอร์: สตรีมหนึ่งสำหรับแสดงตัวอย่าง อีกสตรีมหนึ่งสำหรับการตรวจหาใบหน้า/ฉาก
การประมวลผลเฟรมมีต้นทุนด้านประสิทธิภาพที่สำคัญ และต้นทุนจะเพิ่มขึ้นเมื่อประมวลผลสตรีมหรือไปป์ไลน์แบบขนาน
ทรัพยากรต่างๆ เช่น CPU, GPU และ DSP อาจใช้ประโยชน์จากความสามารถในการประมวลผลซ้ำของเฟรมเวิร์กได้ แต่ทรัพยากรอย่างหน่วยความจำจะเพิ่มขึ้นแบบเชิงเส้น
เป้าหมายหลายรายการต่อคำขอ
คุณรวมสตรีมกล้องหลายรายการเป็นCameraCaptureRequestเดียวได้
ข้อมูลโค้ดต่อไปนี้แสดงวิธีตั้งค่าเซสชันกล้องที่มีสตรีม 1 รายการสำหรับตัวอย่างกล้องและอีกสตรีมหนึ่งสำหรับการประมวลผลรูปภาพ
Kotlin
val session: CameraCaptureSession = ... // from CameraCaptureSession.StateCallback // You will use the preview capture template for the combined streams // because it is optimized for low latency; for high-quality images, use // TEMPLATE_STILL_CAPTURE, and for a steady frame rate use TEMPLATE_RECORD val requestTemplate = CameraDevice.TEMPLATE_PREVIEW val combinedRequest = session.device.createCaptureRequest(requestTemplate) // Link the Surface targets with the combined request combinedRequest.addTarget(previewSurface) combinedRequest.addTarget(imReaderSurface) // In this simple case, the SurfaceView gets updated automatically. ImageReader // has its own callback that you have to listen to in order to retrieve the // frames so there is no need to set up a callback for the capture request session.setRepeatingRequest(combinedRequest.build(), null, null)
Java
CameraCaptureSession session = …; // from CameraCaptureSession.StateCallback // You will use the preview capture template for the combined streams // because it is optimized for low latency; for high-quality images, use // TEMPLATE_STILL_CAPTURE, and for a steady frame rate use TEMPLATE_RECORD CaptureRequest.Builder combinedRequest = session.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); // Link the Surface targets with the combined request combinedRequest.addTarget(previewSurface); combinedRequest.addTarget(imReaderSurface); // In this simple case, the SurfaceView gets updated automatically. ImageReader // has its own callback that you have to listen to in order to retrieve the // frames so there is no need to set up a callback for the capture request session.setRepeatingRequest(combinedRequest.build(), null, null);
หากกำหนดค่าแพลตฟอร์มเป้าหมายอย่างถูกต้อง โค้ดนี้จะสร้างเฉพาะสตรีมที่ตรงตาม FPS ขั้นต่ำที่กำหนดโดย
StreamComfigurationMap.GetOutputMinFrameDuration(int, Size)
และ
StreamComfigurationMap.GetOutputStallDuration(int, Size)
ประสิทธิภาพจริงจะแตกต่างกันไปในแต่ละอุปกรณ์ แต่ Android มีการรับประกันบางอย่าง
สำหรับการรองรับการผสมผสานที่เฉพาะเจาะจงโดยขึ้นอยู่กับ 3 ตัวแปร ได้แก่ ประเภทเอาต์พุต ขนาดเอาต์พุต และระดับฮาร์ดแวร์
การใช้ชุดตัวแปรที่ไม่รองรับอาจทำงานที่อัตราเฟรมต่ำ หากไม่ทำงาน ระบบจะทริกเกอร์หนึ่งในโค้ดเรียกกลับที่ล้มเหลว
เอกสารประกอบสำหรับ createCaptureSession
อธิบายสิ่งที่รับประกันว่าจะใช้งานได้
ประเภทเอาต์พุต
ประเภทเอาต์พุตหมายถึงรูปแบบที่เข้ารหัสเฟรม ค่าที่เป็นไปได้คือ PRIV, YUV, JPEG และ RAW เอกสารประกอบสำหรับ
createCaptureSession
จะอธิบายถึงฟีเจอร์เหล่านี้
เมื่อเลือกประเภทเอาต์พุตของแอปพลิเคชัน หากเป้าหมายคือการเพิ่มความ
เข้ากันได้ ให้ใช้
ImageFormat.YUV_420_888
สําหรับการวิเคราะห์เฟรมและ
ImageFormat.JPEGสําหรับภาพ
นิ่ง สำหรับสถานการณ์การแสดงตัวอย่างและการบันทึก คุณอาจใช้
SurfaceView
TextureView
MediaRecorder
MediaCodec หรือ
RenderScript.Allocation ใน
กรณีดังกล่าว ให้ไม่ต้องระบุรูปแบบรูปภาพ เพื่อความเข้ากันได้ ระบบจะนับเป็น
ImageFormat.PRIVATE
ไม่ว่ารูปแบบจริงที่ใช้ภายในจะเป็นอย่างไร หากต้องการค้นหารูปแบบที่อุปกรณ์รองรับ
เมื่อระบุCameraCharacteristics
ให้ใช้โค้ดต่อไปนี้
Kotlin
val characteristics: CameraCharacteristics = ... val supportedFormats = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP).outputFormats
Java
CameraCharacteristics characteristics = …; int[] supportedFormats = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP).getOutputFormats();
ขนาดเอาต์พุต
ขนาดเอาต์พุตทั้งหมดที่มีจะแสดงตาม
StreamConfigurationMap.getOutputSizes()
แต่มีเพียง 2 ขนาดเท่านั้นที่เกี่ยวข้องกับความเข้ากันได้ ได้แก่ PREVIEW และ MAXIMUM ขนาด
ทำหน้าที่เป็นขอบเขตบน หากมีขนาด PREVIEW ใช้งานได้ แสดงว่ารายการที่มีขนาดเล็กกว่า PREVIEW ก็จะใช้งานได้เช่นกัน MAXIMUM ก็เช่นกัน
เอกสารประกอบสำหรับ
CameraDevice
อธิบายขนาดเหล่านี้
ขนาดเอาต์พุตที่ใช้ได้จะขึ้นอยู่กับรูปแบบที่เลือก เมื่อระบุ
CameraCharacteristics
และรูปแบบ คุณจะค้นหาขนาดเอาต์พุตที่ใช้ได้ดังนี้
Kotlin
val characteristics: CameraCharacteristics = ... val outputFormat: Int = ... // such as ImageFormat.JPEG val sizes = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) .getOutputSizes(outputFormat)
Java
CameraCharacteristics characteristics = …; int outputFormat = …; // such as ImageFormat.JPEG Size[] sizes = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) .getOutputSizes(outputFormat);
ในกรณีการใช้งานการแสดงตัวอย่างและการบันทึกของกล้อง ให้ใช้คลาสเป้าหมายเพื่อกำหนดขนาดที่รองรับ เฟรมเวิร์กของกล้องจะจัดการรูปแบบเอง
Kotlin
val characteristics: CameraCharacteristics = ... val targetClass: Class <T> = ... // such as SurfaceView::class.java val sizes = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) .getOutputSizes(targetClass)
Java
CameraCharacteristics characteristics = …; int outputFormat = …; // such as ImageFormat.JPEG Size[] sizes = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) .getOutputSizes(outputFormat);
หากต้องการรับขนาด MAXIMUM ให้จัดเรียงขนาดเอาต์พุตตามพื้นที่และส่งคืนขนาดที่ใหญ่ที่สุด
Kotlin
fun <T>getMaximumOutputSize( characteristics: CameraCharacteristics, targetClass: Class <T>, format: Int? = null): Size { val config = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) // If image format is provided, use it to determine supported sizes; or else use target class val allSizes = if (format == null) config.getOutputSizes(targetClass) else config.getOutputSizes(format) return allSizes.maxBy { it.height * it.width } }
Java
@RequiresApi(api = Build.VERSION_CODES.N) <T> Size getMaximumOutputSize(CameraCharacteristics characteristics, Class <T> targetClass, Integer format) { StreamConfigurationMap config = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); // If image format is provided, use it to determine supported sizes; else use target class Size[] allSizes; if (format == null) { allSizes = config.getOutputSizes(targetClass); } else { allSizes = config.getOutputSizes(format); } return Arrays.stream(allSizes).max(Comparator.comparing(s -> s.getHeight() * s.getWidth())).get(); }
PREVIEW หมายถึงขนาดที่ตรงกับความละเอียดหน้าจอของอุปกรณ์หรือ 1080p (1920x1080) มากที่สุด ไม่ว่าขนาดใดจะเล็กกว่า สัดส่วนภาพอาจไม่ตรงกับสัดส่วนภาพของหน้าจอพอดี คุณจึงอาจต้องใช้การแสดงภาพแบบจอกว้างหรือการครอบตัดกับสตรีมเพื่อแสดงในโหมดเต็มหน้าจอ หากต้องการดูตัวอย่างขนาดที่ถูกต้อง ให้เปรียบเทียบขนาดเอาต์พุตที่มีกับขนาดการแสดงผลโดย
คำนึงถึงว่าอาจมีการหมุนจอแสดงผล
โค้ดต่อไปนี้กำหนดคลาสตัวช่วย SmartSize ซึ่งจะช่วยให้การเปรียบเทียบขนาด
ง่ายขึ้นเล็กน้อย
Kotlin
/** Helper class used to pre-compute shortest and longest sides of a [Size] */ class SmartSize(width: Int, height: Int) { var size = Size(width, height) var long = max(size.width, size.height) var short = min(size.width, size.height) override fun toString() = "SmartSize(${long}x${short})" } /** Standard High Definition size for pictures and video */ val SIZE_1080P: SmartSize = SmartSize(1920, 1080) /** Returns a [SmartSize] object for the given [Display] */ fun getDisplaySmartSize(display: Display): SmartSize { val outPoint = Point() display.getRealSize(outPoint) return SmartSize(outPoint.x, outPoint.y) } /** * Returns the largest available PREVIEW size. For more information, see: * https://d.android.com/reference/android/hardware/camera2/CameraDevice */ fun <T>getPreviewOutputSize( display: Display, characteristics: CameraCharacteristics, targetClass: Class <T>, format: Int? = null ): Size { // Find which is smaller: screen or 1080p val screenSize = getDisplaySmartSize(display) val hdScreen = screenSize.long >= SIZE_1080P.long || screenSize.short >= SIZE_1080P.short val maxSize = if (hdScreen) SIZE_1080P else screenSize // If image format is provided, use it to determine supported sizes; else use target class val config = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)!! if (format == null) assert(StreamConfigurationMap.isOutputSupportedFor(targetClass)) else assert(config.isOutputSupportedFor(format)) val allSizes = if (format == null) config.getOutputSizes(targetClass) else config.getOutputSizes(format) // Get available sizes and sort them by area from largest to smallest val validSizes = allSizes .sortedWith(compareBy { it.height * it.width }) .map { SmartSize(it.width, it.height) }.reversed() // Then, get the largest output size that is smaller or equal than our max size return validSizes.first { it.long <= maxSize.long && it.short <= maxSize.short }.size }
Java
/** Helper class used to pre-compute shortest and longest sides of a [Size] */ class SmartSize { Size size; double longSize; double shortSize; public SmartSize(Integer width, Integer height) { size = new Size(width, height); longSize = max(size.getWidth(), size.getHeight()); shortSize = min(size.getWidth(), size.getHeight()); } @Override public String toString() { return String.format("SmartSize(%sx%s)", longSize, shortSize); } } /** Standard High Definition size for pictures and video */ SmartSize SIZE_1080P = new SmartSize(1920, 1080); /** Returns a [SmartSize] object for the given [Display] */ SmartSize getDisplaySmartSize(Display display) { Point outPoint = new Point(); display.getRealSize(outPoint); return new SmartSize(outPoint.x, outPoint.y); } /** * Returns the largest available PREVIEW size. For more information, see: * https://d.android.com/reference/android/hardware/camera2/CameraDevice */ @RequiresApi(api = Build.VERSION_CODES.N) <T> Size getPreviewOutputSize( Display display, CameraCharacteristics characteristics, Class <T> targetClass, Integer format ){ // Find which is smaller: screen or 1080p SmartSize screenSize = getDisplaySmartSize(display); boolean hdScreen = screenSize.longSize >= SIZE_1080P.longSize || screenSize.shortSize >= SIZE_1080P.shortSize; SmartSize maxSize; if (hdScreen) { maxSize = SIZE_1080P; } else { maxSize = screenSize; } // If image format is provided, use it to determine supported sizes; else use target class StreamConfigurationMap config = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); if (format == null) assert(StreamConfigurationMap.isOutputSupportedFor(targetClass)); else assert(config.isOutputSupportedFor(format)); Size[] allSizes; if (format == null) { allSizes = config.getOutputSizes(targetClass); } else { allSizes = config.getOutputSizes(format); } // Get available sizes and sort them by area from largest to smallest List <Size> sortedSizes = Arrays.asList(allSizes); List <SmartSize> validSizes = sortedSizes.stream() .sorted(Comparator.comparing(s -> s.getHeight() * s.getWidth())) .map(s -> new SmartSize(s.getWidth(), s.getHeight())) .sorted(Collections.reverseOrder()).collect(Collectors.toList()); // Then, get the largest output size that is smaller or equal than our max size return validSizes.stream() .filter(s -> s.longSize <= maxSize.longSize && s.shortSize <= maxSize.shortSize) .findFirst().get().size; }
ตรวจสอบระดับฮาร์ดแวร์ที่รองรับ
หากต้องการตรวจสอบความสามารถที่พร้อมใช้งานขณะรันไทม์ ให้ตรวจสอบระดับฮาร์ดแวร์ที่รองรับโดยใช้ CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL
ออบเจ็กต์
CameraCharacteristics
ช่วยให้คุณดึงข้อมูลระดับฮาร์ดแวร์ได้ด้วยคำสั่งเดียว
Kotlin
val characteristics: CameraCharacteristics = ... // Hardware level will be one of: // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY, // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL, // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED, // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL, // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3 val hardwareLevel = characteristics.get( CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL)
Java
CameraCharacteristics characteristics = ...; // Hardware level will be one of: // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY, // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL, // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED, // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL, // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3 Integer hardwareLevel = characteristics.get( CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
การรวมทุกอย่างเข้าด้วยกัน
ประเภทเอาต์พุต ขนาดเอาต์พุต และระดับฮาร์ดแวร์จะช่วยให้คุณระบุได้ว่าการ
ผสมสตรีมใดที่ใช้ได้ แผนภูมิต่อไปนี้เป็นภาพรวมของ
การกำหนดค่าที่ CameraDevice รองรับที่ระดับฮาร์ดแวร์
LEGACY
| เป้าหมาย 1 | เป้าหมาย 2 | เป้าหมาย 3 | กรณีการใช้งานตัวอย่าง | |||
|---|---|---|---|---|---|---|
| ประเภท | ขนาดสูงสุด | ประเภท | ขนาดสูงสุด | ประเภท | ขนาดสูงสุด | |
PRIV |
MAXIMUM |
การแสดงตัวอย่างแบบง่าย การประมวลผลวิดีโอด้วย GPU หรือการบันทึกวิดีโอแบบไม่มีการแสดงตัวอย่าง | ||||
JPEG |
MAXIMUM |
การจับภาพนิ่งโดยไม่มีช่องมองภาพ | ||||
YUV |
MAXIMUM |
การประมวลผลวิดีโอ/รูปภาพในแอปพลิเคชัน | ||||
PRIV |
PREVIEW |
JPEG |
MAXIMUM |
การถ่ายภาพนิ่งมาตรฐาน | ||
YUV |
PREVIEW |
JPEG |
MAXIMUM |
การประมวลผลในแอปและการจับภาพยังคงทำงาน | ||
PRIV |
PREVIEW |
PRIV |
PREVIEW |
การบันทึกมาตรฐาน | ||
PRIV |
PREVIEW |
YUV |
PREVIEW |
ดูตัวอย่างและประมวลผลในแอป | ||
PRIV |
PREVIEW |
YUV |
PREVIEW |
JPEG |
MAXIMUM |
ยังคงจับภาพและประมวลผลในแอป |
LEGACY คือระดับฮาร์ดแวร์ที่ต่ำที่สุดที่เป็นไปได้ ตารางนี้แสดงให้เห็นว่าอุปกรณ์ทุกเครื่องที่รองรับ Camera2 (API ระดับ 21 ขึ้นไป) สามารถส่งออกสตรีมพร้อมกันได้สูงสุด 3 รายการโดยใช้การกำหนดค่าที่เหมาะสม และหากไม่มีค่าใช้จ่ายมากเกินไปที่จำกัดประสิทธิภาพ เช่น ข้อจำกัดด้านหน่วยความจำ, CPU หรือความร้อน
นอกจากนี้ แอปยังต้องกำหนดค่าบัฟเฟอร์เอาต์พุตการกำหนดเป้าหมายด้วย เช่น หากต้องการ
กำหนดเป้าหมายอุปกรณ์ที่มีฮาร์ดแวร์ระดับ LEGACY คุณสามารถตั้งค่าเอาต์พุตเป้าหมาย 2 รายการ
โดยรายการหนึ่งใช้ ImageFormat.PRIVATE และอีกรายการใช้
ImageFormat.YUV_420_888 นี่คือการผสมผสานที่รองรับขณะใช้
PREVIEW ขนาด การใช้ฟังก์ชันที่กำหนดไว้ก่อนหน้านี้ในหัวข้อนี้ การรับ
ขนาดตัวอย่างที่จำเป็นสำหรับรหัสกล้องต้องใช้โค้ดต่อไปนี้
Kotlin
val characteristics: CameraCharacteristics = ... val context = this as Context // assuming you are inside of an activity val surfaceViewSize = getPreviewOutputSize( context, characteristics, SurfaceView::class.java) val imageReaderSize = getPreviewOutputSize( context, characteristics, ImageReader::class.java, format = ImageFormat.YUV_420_888)
Java
CameraCharacteristics characteristics = ...; Context context = this; // assuming you are inside of an activity Size surfaceViewSize = getPreviewOutputSize( context, characteristics, SurfaceView.class); Size imageReaderSize = getPreviewOutputSize( context, characteristics, ImageReader.class, format = ImageFormat.YUV_420_888);
โดยต้องรอจนกว่า SurfaceView จะพร้อมใช้งานโดยใช้การเรียกกลับที่ระบุ
Kotlin
val surfaceView = findViewById <SurfaceView>(...) surfaceView.holder.addCallback(object : SurfaceHolder.Callback { override fun surfaceCreated(holder: SurfaceHolder) { // You do not need to specify image format, and it will be considered of type PRIV // Surface is now ready and you could use it as an output target for CameraSession } ... })
Java
SurfaceView surfaceView = findViewById <SurfaceView>(...); surfaceView.getHolder().addCallback(new SurfaceHolder.Callback() { @Override public void surfaceCreated(@NonNull SurfaceHolder surfaceHolder) { // You do not need to specify image format, and it will be considered of type PRIV // Surface is now ready and you could use it as an output target for CameraSession } ... });
คุณสามารถบังคับให้ SurfaceView ตรงกับขนาดเอาต์พุตของกล้องได้โดยเรียกใช้
SurfaceHolder.setFixedSize()
หรือจะใช้วิธีการที่คล้ายกับ
AutoFitSurfaceView จากโมดูล Common
ของตัวอย่างกล้องใน GitHub ก็ได้ ซึ่งจะกำหนดขนาดที่แน่นอนโดยคำนึงถึงทั้งสัดส่วนภาพและพื้นที่ว่าง พร้อมทั้งปรับโดยอัตโนมัติเมื่อมีการเปลี่ยนแปลงกิจกรรม
การตั้งค่าแพลตฟอร์มอื่นๆ จาก
ImageReader ด้วยรูปแบบที่ต้องการจะ
ง่ายกว่า เนื่องจากไม่ต้องรอการเรียกกลับ
Kotlin
val frameBufferCount = 3 // just an example, depends on your usage of ImageReader val imageReader = ImageReader.newInstance( imageReaderSize.width, imageReaderSize.height, ImageFormat.YUV_420_888, frameBufferCount)
Java
int frameBufferCount = 3; // just an example, depends on your usage of ImageReader ImageReader imageReader = ImageReader.newInstance( imageReaderSize.width, imageReaderSize.height, ImageFormat.YUV_420_888, frameBufferCount);
เมื่อใช้บัฟเฟอร์เป้าหมายการบล็อก เช่น ImageReader ให้ทิ้งเฟรมหลังจากใช้แล้ว
Kotlin
imageReader.setOnImageAvailableListener({ val frame = it.acquireNextImage() // Do something with "frame" here it.close() }, null)
Java
imageReader.setOnImageAvailableListener(listener -> { Image frame = listener.acquireNextImage(); // Do something with "frame" here listener.close(); }, null);
LEGACYระดับฮาร์ดแวร์จะกำหนดเป้าหมายไปยังอุปกรณ์ที่มีข้อกำหนดจำนวนหารร่วมน้อย คุณสามารถ
เพิ่มการแตกแขนงแบบมีเงื่อนไขและใช้ขนาด RECORD สำหรับพื้นผิวเป้าหมายเอาต์พุตรายการใดรายการหนึ่ง
ในอุปกรณ์ที่มีระดับฮาร์ดแวร์ LIMITED หรือจะเพิ่มเป็นขนาด
MAXIMUM สำหรับอุปกรณ์ที่มีระดับฮาร์ดแวร์ FULL ก็ได้