Yêu cầu và phiên chụp của máy ảnh

Lưu ý: Trang này đề cập đến gói Camera2. Bạn nên sử dụng CameraX, trừ phi ứng dụng yêu cầu các tính năng cụ thể ở cấp thấp trong Camera2. Cả CameraX và Camera2 đều hỗ trợ Android 5.0 (API cấp 21) trở lên.

Một thiết bị chạy Android có thể có nhiều camera. Mỗi máy ảnh là một CameraDeviceCameraDevice có thể xuất nhiều luồng cùng lúc.

Một lý do cho việc này là để một luồng, các khung máy ảnh tuần tự đến từ CameraDevice, được tối ưu hoá cho một tác vụ cụ thể, chẳng hạn như hiển thị kính ngắm, trong khi các luồng khác có thể được dùng để chụp ảnh hoặc quay video.Các luồng này đóng vai trò như các quy trình song song xử lý từng khung hình thô từ máy ảnh,từng khung hình:

Hình 1. Hình minh hoạ cách xây dựng một ứng dụng máy ảnh toàn cầu (Google I/O 2018)

Quá trình xử lý song song cho thấy có thể có giới hạn về hiệu suất tuỳ thuộc vào công suất xử lý hiện có của CPU, GPU hoặc bộ xử lý khác. Nếu một quy trình không thể theo kịp các khung hình sắp đến, thì quy trình đó sẽ bắt đầu bỏ các khung hình đó.

Mỗi quy trình có định dạng đầu ra riêng. Dữ liệu thô nhận được sẽ được tự động chuyển đổi sang định dạng đầu ra thích hợp thông qua logic ngầm được liên kết với từng quy trình. CameraDevice được sử dụng trong các mã mẫu của trang này là không cụ thể. Vì vậy, trước tiên, bạn cần liệt kê tất cả các máy ảnh có sẵn trước khi tiếp tục.

Bạn có thể sử dụng CameraDevice để tạo CameraCaptureSession, dành riêng cho CameraDevice đó. CameraDevice phải nhận được cấu hình khung cho từng khung thô bằng cách sử dụng CameraCaptureSession. Cấu hình này chỉ định các thuộc tính của máy ảnh như tự động lấy nét, khẩu độ, hiệu ứng và độ phơi sáng. Do các hạn chế về phần cứng, chỉ một cấu hình duy nhất hoạt động trong cảm biến máy ảnh tại một thời điểm bất kỳ, cấu hình này được gọi là cấu hình đang hoạt động.

Tuy nhiên, Trường hợp sử dụng phát trực tuyến giúp cải thiện và mở rộng các cách sử dụng CameraDevice trước đây để phát trực tuyến các phiên chụp, giúp bạn tối ưu hoá luồng camera cho trường hợp sử dụng cụ thể của mình. Ví dụ: có thể cải thiện thời lượng pin khi tối ưu hoá cuộc gọi video.

CameraCaptureSession mô tả tất cả các quy trình có thể có được liên kết với CameraDevice. Khi một phiên được tạo, bạn không thể thêm hoặc xoá quy trình. CameraCaptureSession duy trì một hàng đợi gồm CaptureRequest, hàng đợi này sẽ trở thành cấu hình hoạt động.

CaptureRequest thêm một cấu hình vào hàng đợi và chọn một, nhiều hoặc tất cả các quy trình có sẵn để nhận khung hình từ CameraDevice. Bạn có thể gửi nhiều yêu cầu chụp trong thời gian diễn ra một phiên chụp. Mỗi yêu cầu có thể thay đổi cấu hình đang hoạt động và tập hợp các quy trình đầu ra nhận hình ảnh thô.

Dùng các trường hợp sử dụng luồng để đạt được hiệu suất tốt hơn

Trường hợp sử dụng phát trực tuyến là một cách để cải thiện hiệu suất của các phiên chụp Camera2. Loại dữ liệu này cung cấp cho thiết bị phần cứng nhiều thông tin hơn để điều chỉnh các tham số, nhờ đó mang lại trải nghiệm camera tốt hơn cho tác vụ cụ thể của bạn.

Việc này cho phép thiết bị máy ảnh tối ưu hoá quy trình phần cứng và phần mềm máy ảnh dựa trên kịch bản của người dùng cho từng luồng. Để biết thêm thông tin về các trường hợp sử dụng phát trực tuyến, hãy xem setStreamUseCase.

Trường hợp sử dụng phát trực tuyến cho phép bạn chỉ định cách sử dụng một luồng camera cụ thể một cách chi tiết hơn, ngoài việc đặt mẫu trong CameraDevice.createCaptureRequest(). Điều này cho phép phần cứng máy ảnh tối ưu hoá các tham số (chẳng hạn như việc điều chỉnh, chế độ cảm biến hoặc các chế độ cài đặt cảm biến của máy ảnh) dựa trên các yếu tố đánh đổi chất lượng hoặc độ trễ phù hợp với các trường hợp sử dụng cụ thể.

Các trường hợp sử dụng phát trực tuyến bao gồm:

  • DEFAULT: Bao gồm tất cả hành vi hiện tại của ứng dụng. Việc này tương đương với việc không thiết lập bất kỳ trường hợp sử dụng phát trực tuyến nào.

  • PREVIEW: Nên dùng cho Kính ngắm hoặc tính năng phân tích hình ảnh trong ứng dụng.

  • STILL_CAPTURE: Được tối ưu hoá để chụp ảnh chất lượng cao ở độ phân giải cao, và không dự kiến duy trì tốc độ khung hình giống như bản xem trước.

  • VIDEO_RECORD: Được tối ưu hoá để quay video chất lượng cao, bao gồm cả tính năng ổn định hình ảnh chất lượng cao, nếu được thiết bị hỗ trợ và được ứng dụng bật. Tuỳ chọn này có thể tạo ra các khung hình đầu ra có độ trễ đáng kể so với thời gian thực, để đảm bảo chất lượng ổn định hoặc quá trình xử lý khác ở chất lượng cao nhất.

  • VIDEO_CALL: Nên dùng cho máy ảnh chạy trong thời gian dài khi gây ra vấn đề tiêu hao điện năng.

  • PREVIEW_VIDEO_STILL: Nên dùng cho ứng dụng mạng xã hội hoặc trường hợp sử dụng một luồng. Đây là một luồng đa năng.

  • VENDOR_START: Dùng cho các trường hợp sử dụng do OEM xác định.

Tạo CameraCaptureSession

Để tạo một phiên máy ảnh, hãy cung cấp cho phiên đó một hoặc nhiều vùng đệm đầu ra mà ứng dụng của bạn có thể ghi các khung hình đầu ra. Mỗi vùng đệm đại diện cho một quy trình. Bạn phải thực hiện việc này trước khi bắt đầu sử dụng máy ảnh để khung có thể định cấu hình quy trình nội bộ của thiết bị và phân bổ vùng đệm bộ nhớ nhằm gửi khung đến các mục tiêu đầu ra cần thiết.

Đoạn mã sau đây cho biết cách bạn có thể chuẩn bị một phiên máy ảnh với hai vùng đệm đầu ra, một vùng thuộc về SurfaceView và một vùng khác thuộc về ImageReader. Việc thêm PREVIEW Trường hợp sử dụng phát trực tuyến vào previewSurfaceSTILL_CAPTURE Trường hợp sử dụng phát trực tuyến vào imReaderSurface cho phép phần cứng thiết bị tối ưu hoá những luồng này hơn nữa.

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

Tại thời điểm này, bạn chưa xác định cấu hình đang hoạt động của máy ảnh. Khi phiên được định cấu hình, bạn có thể tạo và gửi các yêu cầu chụp ảnh để thực hiện việc đó.

Phép biến đổi áp dụng cho dữ liệu đầu vào khi chúng được ghi vào vùng đệm được xác định theo loại của từng mục tiêu và phải là Surface. Khung Android biết cách chuyển đổi hình ảnh thô trong cấu hình đang hoạt động sang định dạng phù hợp với từng mục tiêu. Việc chuyển đổi được kiểm soát bởi định dạng và kích thước pixel của Surface cụ thể.

Khung này cố gắng hết sức, nhưng một số tổ hợp cấu hình Surface có thể không hoạt động, gây ra các vấn đề như phiên không được tạo, báo lỗi thời gian chạy khi bạn gửi yêu cầu hoặc giảm hiệu suất. Khung này đảm bảo các tổ hợp thiết bị, nền tảng và yêu cầu cụ thể. Tài liệu dành cho createCaptureSession() cung cấp thêm thông tin.

Một yêu cầu chụp

Cấu hình dùng cho mỗi khung hình được mã hoá trong CaptureRequest và được gửi đến máy ảnh. Để tạo yêu cầu chụp, bạn có thể sử dụng một trong các mẫu được xác định trước, hoặc bạn có thể sử dụng TEMPLATE_MANUAL để kiểm soát hoàn toàn. Khi chọn một mẫu, bạn cần cung cấp một hoặc nhiều vùng đệm đầu ra để sử dụng với yêu cầu. Bạn chỉ có thể sử dụng vùng đệm đã được xác định trong phiên chụp mà bạn định sử dụng.

Các yêu cầu chụp sử dụng mẫu trình tạo và giúp nhà phát triển có cơ hội đặt nhiều tuỳ chọn, bao gồm tự động phơi sáng, tự động lấy nétkhẩu độ ống kính. Trước khi đặt một trường, hãy đảm bảo thiết bị có tuỳ chọn cụ thể bằng cách gọi CameraCharacteristics.getAvailableCaptureRequestKeys() và giá trị mong muốn được hỗ trợ bằng cách kiểm tra đặc điểm của máy ảnh thích hợp, chẳng hạn như các chế độ phơi sáng tự động có sẵn.

Để tạo yêu cầu chụp cho SurfaceView bằng cách sử dụng mẫu được thiết kế để xem trước mà không cần sửa đổi, hãy sử dụng 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);

Với yêu cầu chụp ảnh đã được xác định, giờ đây bạn có thể gửi yêu cầu đó đến phiên máy ảnh:

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);

Khi một khung đầu ra được đưa vào vùng đệm cụ thể, lệnh gọi lại chụp sẽ được kích hoạt. Trong nhiều trường hợp, các lệnh gọi lại bổ sung, chẳng hạn như ImageReader.OnImageAvailableListener, được kích hoạt khi khung có trong đó được xử lý. Tại thời điểm này, bạn có thể truy xuất dữ liệu hình ảnh từ vùng đệm được chỉ định.

Lặp lại yêu cầu chụp

Rất dễ thực hiện các yêu cầu về một máy ảnh, nhưng không thực sự hữu ích để hiển thị bản xem trước trực tiếp hoặc video. Trong trường hợp đó, bạn cần nhận được một luồng khung hình liên tục chứ không chỉ một khung hình duy nhất. Đoạn mã sau đây cho biết cách thêm một yêu cầu lặp lại vào phiên:

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);

Yêu cầu chụp lặp lại giúp thiết bị máy ảnh liên tục chụp ảnh bằng cách sử dụng các chế độ cài đặt trong CaptureRequest được cung cấp. API Camera2 cũng cho phép người dùng quay video bằng máy ảnh bằng cách gửi CaptureRequests lặp lại như bạn thấy trong kho lưu trữ mẫu Camera2 này trên GitHub. Ứng dụng này cũng có thể kết xuất video chuyển động chậm bằng cách quay video tốc độ cao (chuyển động chậm) bằng cách sử dụng loạt ảnh lặp lại CaptureRequests như minh hoạ trong ứng dụng mẫu video chuyển động chậm của Camera2 trên GitHub.

CaptureRequest xen kẽ

Để gửi yêu cầu chụp thứ hai trong khi yêu cầu chụp lặp lại đang hoạt động, chẳng hạn như để hiển thị kính ngắm và cho phép người dùng chụp ảnh, bạn không cần dừng yêu cầu lặp lại đang diễn ra. Thay vào đó, bạn đưa ra yêu cầu chụp không lặp lại trong khi yêu cầu lặp lại sẽ tiếp tục chạy.

Bạn cần định cấu hình mọi vùng đệm đầu ra được sử dụng trong phiên máy ảnh khi phiên được tạo lần đầu tiên. Các yêu cầu lặp lại có mức độ ưu tiên thấp hơn so với các yêu cầu một khung hoặc hàng loạt, cho phép mẫu sau đây hoạt động:

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);

Tuy nhiên, có một hạn chế của phương pháp này: bạn sẽ không biết chính xác thời điểm một yêu cầu duy nhất xảy ra. Trong hình sau, nếu A là yêu cầu chụp ảnh lặp lại và B là yêu cầu chụp một khung hình, thì đây là cách phiên xử lý hàng đợi yêu cầu:

Hình 2. Hình minh hoạ hàng đợi yêu cầu cho phiên camera đang diễn ra

Không có gì đảm bảo về độ trễ giữa yêu cầu lặp lại gần đây nhất từ A trước khi yêu cầu B kích hoạt và lần tiếp theo A được sử dụng trở lại, vì vậy, bạn có thể gặp phải một số khung hình bị bỏ qua. Bạn có thể làm một số việc để giảm thiểu vấn đề này:

  • Thêm các mục tiêu đầu ra từ yêu cầu A để yêu cầu B. Nhờ đó, khi khung hình của B đã sẵn sàng, khung đó sẽ được sao chép vào các mục tiêu đầu ra của A. Ví dụ: điều này rất cần thiết khi thực hiện ảnh chụp nhanh video để duy trì tốc độ khung hình ổn định. Trong mã trước, bạn thêm singleRequest.addTarget(previewSurface) trước khi tạo yêu cầu.

  • Sử dụng kết hợp các mẫu được thiết kế phù hợp với tình huống cụ thể này, chẳng hạn như độ trễ màn trập bằng 0.