เซสชันและคำขอบันทึกภาพของกล้อง

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

อุปกรณ์ที่ใช้ระบบ Android เครื่องเดียวสามารถมีกล้องได้หลายตัว กล้องแต่ละตัว CameraDevice และ CameraDevice จะแสดงสตรีม มากกว่า 1 รายการพร้อมกันได้

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

วันที่
รูปที่ 1 ภาพจากการสร้างแอป Universal Camera (Google I/O ‘18)

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

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

คุณสามารถใช้ CameraDevice เพื่อสร้าง CameraCaptureSession, โดยเฉพาะสำหรับ CameraDevice ดังกล่าว CameraDevice ต้องได้รับแอตทริบิวต์ การกำหนดค่าเฟรมสำหรับเฟรมดิบแต่ละเฟรมโดยใช้ CameraCaptureSession การกำหนดค่า จะระบุคุณลักษณะของกล้อง เช่น โฟกัสอัตโนมัติ รูรับแสง เอฟเฟกต์ และจำนวนผู้ที่เห็น เนื่องจากข้อจำกัดด้านฮาร์ดแวร์ การกำหนดค่าแบบใดแบบหนึ่งเท่านั้น อยู่ในเซ็นเซอร์กล้องในช่วงเวลาใดเวลาหนึ่ง ซึ่งเรียกว่า active

อย่างไรก็ตาม กรณีการใช้งานของสตรีมจะเพิ่มประสิทธิภาพและขยายขอบเขตของการใช้ CameraDevice ก่อนหน้านี้ เพื่อสตรีมเซสชันการจับภาพ ซึ่งจะช่วยให้คุณเพิ่มประสิทธิภาพสตรีมของกล้องสำหรับ Use Case เฉพาะ เช่น ปรับปรุงอายุการใช้งานแบตเตอรี่เมื่อเพิ่มประสิทธิภาพ วิดีโอคอล

CameraCaptureSession จะอธิบายไปป์ไลน์ทั้งหมดที่เป็นไปได้ซึ่งผูกกับ CameraDevice เมื่อสร้างเซสชันแล้ว คุณจะเพิ่มหรือนำไปป์ไลน์ออกไม่ได้ CameraCaptureSession รักษาคิวของ CaptureRequest วินาที ซึ่งจะกลายเป็นการกำหนดค่าที่ใช้งานอยู่

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

ใช้กรณีการใช้งานของสตรีมเพื่อประสิทธิภาพที่ดีขึ้น

กรณีการใช้งานของสตรีมเป็นวิธีปรับปรุงประสิทธิภาพการจับภาพของ Camera2 เซสชัน โดยให้ข้อมูลเพิ่มเติมแก่อุปกรณ์ฮาร์ดแวร์ในการปรับแต่งพารามิเตอร์ ซึ่งมอบประสบการณ์การใช้งานกล้องที่ดียิ่งขึ้นสำหรับงานที่เฉพาะเจาะจงของคุณ

ช่วงเวลานี้ ช่วยให้อุปกรณ์กล้องเพิ่มประสิทธิภาพฮาร์ดแวร์กล้องและไปป์ไลน์ซอฟต์แวร์ ตามสถานการณ์ของผู้ใช้สำหรับแต่ละสตรีม ดูข้อมูลเพิ่มเติมเกี่ยวกับการใช้สตรีม กรณีต่างๆ โปรดดู setStreamUseCase

กรณีการใช้งานของสตรีมช่วยให้คุณระบุวิธีใช้สตรีมจากกล้องหนึ่งๆ ได้ใน รายละเอียดมากขึ้น นอกเหนือจากการตั้งค่าเทมเพลตใน CameraDevice.createCaptureRequest() วิธีนี้จะช่วยให้ฮาร์ดแวร์กล้องเพิ่มประสิทธิภาพได้ เช่น การปรับแต่ง โหมดเซ็นเซอร์ หรือการตั้งค่าเซ็นเซอร์ของกล้องตาม จะเกี่ยวข้องกับคุณภาพหรือเวลาในการตอบสนองที่เหมาะกับ Use Case ที่เฉพาะเจาะจง

กรณีการใช้งานของสตรีมมีดังนี้

  • DEFAULT: ครอบคลุมการทำงานของแอปพลิเคชันที่มีอยู่ทั้งหมด เทียบเท่ากับการไม่ ตั้งค่า Use Case ของสตรีม

  • PREVIEW: แนะนำสำหรับการวิเคราะห์ช่องมองภาพหรือรูปภาพในแอป

  • STILL_CAPTURE: เพิ่มประสิทธิภาพเพื่อการถ่ายภาพความละเอียดสูง ไม่ใช่ จะยังคงรักษาอัตราเฟรมเหมือนตัวอย่างได้

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

  • VIDEO_CALL: แนะนำสำหรับกล้องที่ใช้งานเป็นเวลานานเมื่อการระบายพลังงาน ข้อกังวล

  • PREVIEW_VIDEO_STILL: แนะนำสำหรับแอปโซเชียลมีเดียหรือการใช้สตรีมเดียว กรณี เป็นสตรีมอเนกประสงค์

  • VENDOR_START: ใช้สำหรับ Use Case ที่ OEM กำหนด

สร้าง CameraCaptureSession

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

ข้อมูลโค้ดต่อไปนี้จะแสดงวิธีเตรียมเซสชันของกล้อง เอาต์พุตบัฟเฟอร์ซึ่งเป็นของ SurfaceView และอีกโดเมนไปยัง ImageReader การเพิ่มกรณีการใช้งานสตรีม PREVIEW ไปยัง previewSurface และ การใช้สตรีม STILL_CAPTURE เคสที่จะimReaderSurfaceช่วยให้ฮาร์ดแวร์ของอุปกรณ์เพิ่มประสิทธิภาพสตรีมเหล่านี้ได้แม้ ต่อไป

Kotlin


// Retrieve the target surfaces, which might be coming from a number of places:
// 1. SurfaceView, if you want to display the image directly to the user
// 2. ImageReader, if you want to read each frame or perform frame-by-frame
// analysis
// 3. OpenGL Texture or TextureView, although discouraged for maintainability
      reasons
// 4. RenderScript.Allocation, if you want to do parallel processing
val surfaceView = findViewById<SurfaceView>(...)
val imageReader = ImageReader.newInstance(...)

// Remember to call this only *after* SurfaceHolder.Callback.surfaceCreated()
val previewSurface = surfaceView.holder.surface
val imReaderSurface = imageReader.surface
val targets = listOf(previewSurface, imReaderSurface)

// Create a capture session using the predefined targets; this also involves
// defining the session state callback to be notified of when the session is
// ready
// Setup Stream Use Case while setting up your Output Configuration.
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
fun configureSession(device: CameraDevice, targets: List<Surface>){
    val configs = mutableListOf<OutputConfiguration>()
    val streamUseCase = CameraMetadata
        .SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW_VIDEO_STILL

    targets.forEach {
        val config = OutputConfiguration(it)
        config.streamUseCase = streamUseCase.toLong()
        configs.add(config)
    }
    ...
    device.createCaptureSession(session)
}

Java


// Retrieve the target surfaces, which might be coming from a number of places:
// 1. SurfaceView, if you want to display the image directly to the user
// 2. ImageReader, if you want to read each frame or perform frame-by-frame
      analysis
// 3. RenderScript.Allocation, if you want to do parallel processing
// 4. OpenGL Texture or TextureView, although discouraged for maintainability
      reasons
Surface surfaceView = findViewById<SurfaceView>(...);
ImageReader imageReader = ImageReader.newInstance(...);

// Remember to call this only *after* SurfaceHolder.Callback.surfaceCreated()
Surface previewSurface = surfaceView.getHolder().getSurface();
Surface imageSurface = imageReader.getSurface();
List<Surface> targets = Arrays.asList(previewSurface, imageSurface);

// Create a capture session using the predefined targets; this also involves defining the
// session state callback to be notified of when the session is ready
private void configureSession(CameraDevice device, List<Surface> targets){
    ArrayList<OutputConfiguration> configs= new ArrayList()
    String streamUseCase=  CameraMetadata
        .SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW_VIDEO_STILL

    for(Surface s : targets){
        OutputConfiguration config = new OutputConfiguration(s)
        config.setStreamUseCase(String.toLong(streamUseCase))
        configs.add(config)
}

device.createCaptureSession(session)
}

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

การเปลี่ยนรูปแบบที่ใช้กับอินพุตขณะเขียนลงในบัฟเฟอร์คือ ตามประเภทของแต่ละเป้าหมาย ซึ่งต้องเป็น Surface เฟรมเวิร์ก Android รู้วิธี แปลงรูปภาพดิบในการกำหนดค่าที่ใช้งานอยู่เป็นรูปแบบที่เหมาะสมสำหรับ แต่ละเป้าหมาย Conversion ถูกควบคุมโดยรูปแบบพิกเซลและขนาดของฟิลด์ Surface โดยเฉพาะ

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

CaptureRequests เดี่ยว

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

คำขอบันทึกใช้ รูปแบบของเครื่องมือสร้าง และให้โอกาสนักพัฒนาซอฟต์แวร์ในการตั้งค่า ตัวเลือกอื่น ได้แก่ การรับแสงอัตโนมัติ โฟกัสอัตโนมัติ และ รูรับแสงของเลนส์ ก่อนตั้งค่าช่อง โปรดตรวจสอบว่าตัวเลือกที่เจาะจงใช้ได้กับ อุปกรณ์ด้วยการโทร CameraCharacteristics.getAvailableCaptureRequestKeys() และตรวจสอบว่าได้ค่าที่ต้องการด้วยการตรวจสอบกล้องที่เหมาะสม ลักษณะเฉพาะ เช่น การรับแสงอัตโนมัติที่ใช้ได้ โหมดต่างๆ

วิธีสร้างคำขอบันทึกสำหรับ SurfaceView โดยใช้เทมเพลต ออกแบบมาเพื่อการแสดงตัวอย่างโดยไม่ต้องแก้ไขอะไรเลย ใช้ CameraDevice.TEMPLATE_PREVIEW:

Kotlin

val session: CameraCaptureSession = ...  // from CameraCaptureSession.StateCallback
val captureRequest = session.device.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
captureRequest.addTarget(previewSurface)

Java

CameraCaptureSession session = ...;  // from CameraCaptureSession.StateCallback
CaptureRequest.Builder captureRequest =
    session.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
captureRequest.addTarget(previewSurface);

เมื่อกำหนดคำขอบันทึกแล้ว ตอนนี้คุณจะมอบหมายได้ ไปยังเซสชันของกล้อง

Kotlin

val session: CameraCaptureSession = ...  // from CameraCaptureSession.StateCallback
val captureRequest: CaptureRequest = ...  // from CameraDevice.createCaptureRequest()

// The first null argument corresponds to the capture callback, which you
// provide if you want to retrieve frame metadata or keep track of failed capture
// requests that can indicate dropped frames; the second null argument
// corresponds to the Handler used by the asynchronous callback, which falls
// back to the current thread's looper if null
session.capture(captureRequest.build(), null, null)

Java

CameraCaptureSession session = ...;  // from CameraCaptureSession.StateCallback
CaptureRequest captureRequest = ...;  // from CameraDevice.createCaptureRequest()

// The first null argument corresponds to the capture callback, which you
// provide if you want to retrieve frame metadata or keep track of failed
// capture
// requests that can indicate dropped frames; the second null argument
// corresponds to the Handler used by the asynchronous callback, which falls
// back to the current thread's looper if null
session.capture(captureRequest.build(), null, null);

เมื่อมีการใส่เฟรมเอาต์พุตในบัฟเฟอร์ที่เฉพาะเจาะจง จับภาพ Callback มีการทริกเกอร์ ในหลายกรณีของ Callback เพิ่มเติม เช่น ImageReader.OnImageAvailableListener ถูกเรียกใช้เมื่อเฟรมที่อยู่ในเฟรมได้รับการประมวลผล จัดขึ้นในเวลา คุณจึงสามารถเรียกดูข้อมูลภาพจากบัฟเฟอร์ที่ระบุได้

CaptureRequests ซ้ำ

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

Kotlin

val session: CameraCaptureSession = ...  // from CameraCaptureSession.StateCallback
val captureRequest: CaptureRequest = ...  // from CameraDevice.createCaptureRequest()

// This keeps sending the capture request as frequently as possible until
// the
// session is torn down or session.stopRepeating() is called
// session.setRepeatingRequest(captureRequest.build(), null, null)

Java

CameraCaptureSession session = ...;  // from CameraCaptureSession.StateCallback
CaptureRequest captureRequest = ...;  // from CameraDevice.createCaptureRequest()

// This keeps sending the capture request as frequently as possible until the
// session is torn down or session.stopRepeating() is called
// session.setRepeatingRequest(captureRequest.build(), null, null);

คำขอจับภาพซ้ำทำให้อุปกรณ์กล้องจับภาพได้อย่างต่อเนื่อง โดยใช้การตั้งค่าใน CaptureRequest ที่ให้ไว้ Camera2 API ยังช่วยให้ผู้ใช้จับภาพวิดีโอจากกล้องได้โดยการส่ง ทำซ้ำ CaptureRequests ตามที่เห็นใน ตัวอย่าง Camera2 ใน GitHub และยังสามารถแสดงผลวิดีโอสโลว์โมชันด้วยการจับภาพ วิดีโอความเร็วสูง (สโลว์โมชัน) ที่ใช้การถ่ายภาพอัจฉริยะซ้ำ CaptureRequests ตามที่แสดงในแอปตัวอย่างวิดีโอสโลว์โมชัน Camera2 ใน GitHub

การจับภาพคำขอแบบแทรกสลับ

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

ต้องกำหนดค่าบัฟเฟอร์เอาต์พุตที่ใช้ให้เป็นส่วนหนึ่งของเซสชันกล้อง เมื่อสร้างเซสชันเป็นครั้งแรก คำขอซ้ำมีลำดับความสำคัญต่ำกว่า คำขอแบบเฟรมเดียวหรือแบบต่อเนื่อง ซึ่งช่วยให้ตัวอย่างต่อไปนี้ทำงานได้:

Kotlin

val session: CameraCaptureSession = ...  // from CameraCaptureSession.StateCallback

// Create the repeating request and dispatch it
val repeatingRequest = session.device.createCaptureRequest(
CameraDevice.TEMPLATE_PREVIEW)
repeatingRequest.addTarget(previewSurface)
session.setRepeatingRequest(repeatingRequest.build(), null, null)

// Some time later...

// Create the single request and dispatch it
// NOTE: This can disrupt the ongoing repeating request momentarily
val singleRequest = session.device.createCaptureRequest(
CameraDevice.TEMPLATE_STILL_CAPTURE)
singleRequest.addTarget(imReaderSurface)
session.capture(singleRequest.build(), null, null)

Java

CameraCaptureSession session = ...;  // from CameraCaptureSession.StateCallback

// Create the repeating request and dispatch it
CaptureRequest.Builder repeatingRequest =
session.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
repeatingRequest.addTarget(previewSurface);
session.setRepeatingRequest(repeatingRequest.build(), null, null);

// Some time later...

// Create the single request and dispatch it
// NOTE: This can disrupt the ongoing repeating request momentarily
CaptureRequest.Builder singleRequest =
session.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
singleRequest.addTarget(imReaderSurface);
session.capture(singleRequest.build(), null, null);

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

วันที่
รูปที่ 2 ภาพของคิวคำขอสำหรับเซสชันกล้องที่ดำเนินอยู่

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

  • เพิ่มเป้าหมายเอาต์พุตจากคำขอ A เพื่อขอ B วิธีนี้จะทำให้ เฟรมของ B พร้อมใช้งานแล้ว ระบบจะคัดลอกไปยังเป้าหมายเอาต์พุตของ A ตัวอย่างเช่น สิ่งนี้เป็นสิ่งจำเป็นในการสร้างสแนปชอตวิดีโอเพื่อคงรักษา อัตราเฟรมคงที่ ในโค้ดก่อนหน้านี้ คุณเพิ่ม singleRequest.addTarget(previewSurface)ก่อนสร้างคำขอ

  • ใช้เทมเพลตต่างๆ ร่วมกันซึ่งออกแบบมาเพื่อสถานการณ์นี้โดยเฉพาะ เช่น Zero-shutter-lag