Lưu ý: Trang này đề cập đến gói Camera2. Bạn nên sử dụng CameraX, trừ phi ứng dụng của bạn yêu cầu các tính năng cụ thể, cấp thấp của 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 camera là một CameraDevice và một CameraDevice có thể xuất ra nhiều luồng cùng một lúc.
Một lý do để làm việc này là để một luồng (các khung hình 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 hoạt động như các quy trình song song xử lý từng khung hình thô từ máy ả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 khả năng 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 đến, thì quy trình đó sẽ bắt đầu loại bỏ các khung hình.
Mỗi quy trình đều có định dạng đầu ra riêng. Dữ liệu thô được đưa vào sẽ tự động được chuyển đổi thành định dạng đầu ra thích hợp theo logic ngầm định liên kết với từng quy trình. CameraDevice được 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ả camera hiện có 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 mỗi 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 quy tắc ràng buộc về phần cứng, chỉ có một cấu hình duy nhất hoạt động trong cảm biến máy ảnh tại bất kỳ thời điểm nào, được gọi là cấu hình đang hoạt động.
Tuy nhiên, Trường hợp sử dụng luồng video nâng cao và mở rộng các cách sử dụng CameraDevice trước đây để phát trực tuyến phiên chụp, cho phép bạn tối ưu hoá luồng video của camera cho trường hợp sử dụng cụ thể của mình. Ví dụ: tính năng này 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ể 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 các CaptureRequest, trở thành cấu hình đang 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ả 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 suốt thời gian của 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 quy trình đầu ra nhận hình ảnh thô.
Dùng các trường hợp sử dụng phát trực tuyến để có hiệu suất tốt hơn
Trường hợp sử dụng luồng là một cách để cải thiện hiệu suất của phiên chụp ảnh Camera2. Các chế độ này cung cấp cho thiết bị phần cứng thêm thông tin để điều chỉnh các tham số, mang lại trải nghiệm camera tốt hơn cho tác vụ cụ thể của bạn.
Điều 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 của máy ảnh dựa trên các tình huống của người dùng cho mỗi 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 luồng video cho phép bạn chỉ định cách sử dụng luồng video của 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ư điều chỉnh, chế độ cảm biến hoặc chế độ cài đặt tính năng cảm biến của camera, dựa trên sự đánh đổi về 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 luồng bao gồm:
DEFAULT: Bao gồm tất cả hành vi hiện có của ứng dụng. Điều này tương đương với việc không đặt bất kỳ Trường hợp sử dụng luồng nào.PREVIEW: Nên dùng cho Kính ngắm hoặc phân tích hình ảnh trong ứng dụng.STILL_CAPTURE: Được tối ưu hoá để chụp ảnh có độ phân giải cao, chất lượng 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 thiết bị hỗ trợ và ứng dụng bật tính năng này. 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, để cho phép ổn định chất lượng cao nhất hoặc xử lý khác.VIDEO_CALL: Nên dùng cho các trường hợp sử dụng máy ảnh trong thời gian dài, khi mức tiêu hao pin là vấn đề đáng lo ngại.PREVIEW_VIDEO_STILL: Nên dùng cho các ứ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 camera, 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 khung hình đầu ra vào đó. 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ớ để 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 camera với hai vùng đệm đầu ra, một vùng đệm thuộc về SurfaceView và một vùng đệm khác thuộc về ImageReader. Việc thêm Trường hợp sử dụng phát trực tuyến PREVIEW vào previewSurface và Trường hợp sử dụng phát trực tuyến STILL_CAPTURE vào imReaderSurface cho phép phần cứng thiết bị tối ưu hoá các luồng này hơn nữa.
// 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) }
Tại thời điểm này, bạn chưa xác định cấu hình hoạt động của camera. Khi phiên được định cấu hình, bạn có thể tạo và gửi yêu cầu chụp để 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, 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 thành định dạng phù hợp với từng mục tiêu. Quá trình chuyển đổi được kiểm soát bằng định dạng pixel và kích thước 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ư không tạo được phiên, gửi lỗi thời gian chạy khi bạn gửi yêu cầu hoặc làm giảm hiệu suất. Khung này đảm bảo cho các tổ hợp cụ thể của thiết bị, nền tảng và tham số yêu cầu. Tài liệu về createCaptureSession() cung cấp thêm thông tin.
CaptureRequests đơn lẻ
Cấu hình dùng cho mỗi khung hình được mã hoá trong một CaptureRequest, sau đó đượ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 có thể sử dụng TEMPLATE_MANUAL để có toàn quyền kiểm soát. 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 các vùng đệm đã được xác định trong phiên chụp mà bạn định sử dụng.
Yêu cầu chụp sử dụng mẫu trình tạo và cho phép nhà phát triển đặt nhiều tuỳ chọn khác nhau, bao gồm cả tự động phơi sáng, tự động lấy nét và khẩu độ ống kính. Trước khi đặt trường, hãy đảm bảo rằng tuỳ chọn cụ thể có sẵn cho thiết bị bằng cách gọi CameraCharacteristics.getAvailableCaptureRequestKeys() và giá trị đã chọn được hỗ trợ bằng cách kiểm tra đặc điểm máy ảnh thích hợp, chẳng hạn như chế độ tự động phơi sáng có sẵn.
Để tạo yêu cầu chụp cho SurfaceView bằ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:
val session: CameraCaptureSession = /* ... */ // from CameraCaptureSession.StateCallback val captureRequest = session.device.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW) captureRequest.addTarget(previewSurface)
Sau khi xác định yêu cầu chụp, giờ đây, bạn có thể gửi yêu cầu đó đến phiên camera:
val session: CameraCaptureSession = ... // from CameraCaptureSession.StateCallback val captureRequest: CaptureRequest.Builder = ... // 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, sẽ được kích hoạt khi khung chứa lệnh gọi lại đó đượ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 đã chỉ định.
Lặp lại CaptureRequests
Yêu cầu một camera rất dễ thực hiện, nhưng để hiển thị bản xem trước trực tiếp hoặc video, thì yêu cầu này không hữu ích lắm. 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. Đoạn mã sau đây cho biết cách thêm yêu cầu lặp lại vào phiên:
val session: CameraCaptureSession = ... // from CameraCaptureSession.StateCallback val captureRequest: CaptureRequest.Builder = ... // 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 khiến thiết bị camera liên tục chụp ảnh bằng cách sử dụng chế độ cài đặt trong CaptureRequest được cung cấp. Camera2 API cũng cho phép người dùng quay video bằng camera bằng cách gửi CaptureRequests lặp lại như 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 lặp lại loạt ảnh CaptureRequests như minh hoạ trong ứng dụng mẫu video chuyển động chậm Camera2 trên GitHub.
Đan xen CaptureRequests
Để 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 một yêu cầu chụp không lặp lại trong khi yêu cầu lặp lại tiếp tục chạy.
Mọi vùng đệm đầu ra được sử dụng đều cần được định cấu hình trong phiên camera khi phiên đó được tạo lần đầu tiên. Yêu cầu lặp lại có mức độ ưu tiên thấp hơn yêu cầu một khung hình hoặc yêu cầu chụp liên tục, cho phép mẫu sau hoạt động:
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)
Tuy nhiên, phương pháp này có một nhược điểm: bạn không biết chính xác thời điểm yêu cầu duy nhất xảy ra. Trong hình sau, nếu A là yêu cầu chụp 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:
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 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 mục tiêu đầu ra từ yêu cầu A vào yêu cầu B. Bằng cách đó, khi khung hình của B đã sẵn sàng, khung hình đó 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 chụp nhanh video để duy trì tốc độ khung hình ổn định. Trong đoạn 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ế để hoạt động cho trường hợp cụ thể này, chẳng hạn như độ trễ màn trập bằng không.