Lưu ý: Trang này đề cập đến gói Camera2. Bạn nên 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 ứng dụng camera có thể sử dụng đồng thời nhiều luồng khung hình. Trong một số trường hợp, các luồng khác nhau thậm chí còn yêu cầu độ phân giải khung hình hoặc định dạng pixel khác nhau. Sau đây là một số trường hợp sử dụng thường gặp:
- Ghi video: một luồng để xem trước, một luồng khác được mã hoá và lưu vào tệp.
- Quét mã vạch: một luồng để xem trước, một luồng để phát hiện mã vạch.
- Nhiếp ảnh điện toán: một luồng để xem trước, một luồng khác để phát hiện khuôn mặt/cảnh.
Có một chi phí hiệu suất không nhỏ khi xử lý các khung hình và chi phí này sẽ tăng lên khi thực hiện xử lý song song hoặc xử lý theo quy trình.
Các tài nguyên như CPU, GPU và DSP có thể tận dụng các chức năng xử lý lại của khung, nhưng các tài nguyên như bộ nhớ sẽ tăng tuyến tính.
Nhiều mục tiêu cho mỗi yêu cầu
Bạn có thể kết hợp nhiều luồng camera thành một CameraCaptureRequest.
Đoạn mã sau đây minh hoạ cách thiết lập một phiên máy ảnh có một luồng để xem trước camera và một luồng khác để xử lý hình ảnh:
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);
Nếu bạn định cấu hình chính xác các bề mặt đích, mã này sẽ chỉ tạo ra những luồng đáp ứng FPS tối thiểu do StreamComfigurationMap.GetOutputMinFrameDuration(int, Size) và StreamComfigurationMap.GetOutputStallDuration(int, Size) xác định.
Hiệu suất thực tế sẽ khác nhau tuỳ theo từng thiết bị, mặc dù Android đảm bảo hỗ trợ một số tổ hợp cụ thể tuỳ thuộc vào 3 biến số: loại đầu ra, kích thước đầu ra và cấp độ phần cứng.
Việc sử dụng một tổ hợp biến không được hỗ trợ có thể hoạt động ở tốc độ khung hình thấp; nếu không, thao tác này sẽ kích hoạt một trong các lệnh gọi lại thất bại.
Tài liệu về createCaptureSession mô tả những gì chắc chắn sẽ hoạt động.
Loại đầu ra
Loại đầu ra đề cập đến định dạng mà các khung hình được mã hoá. Các giá trị có thể có là PRIV, YUV, JPEG và RAW. Tài liệu cho createCaptureSession mô tả các thành phần này.
Khi chọn loại đầu ra của ứng dụng, nếu mục tiêu là tối đa hoá khả năng tương thích, hãy sử dụng ImageFormat.YUV_420_888 để phân tích khung hình và ImageFormat.JPEG cho ảnh tĩnh. Đối với các tình huống xem trước và ghi hình, bạn có thể sẽ sử dụng SurfaceView, TextureView, MediaRecorder, MediaCodec hoặc RenderScript.Allocation. Trong những trường hợp đó, đừng chỉ định định dạng hình ảnh. Để đảm bảo khả năng tương thích, giá trị này sẽ được tính là ImageFormat.PRIVATE, bất kể định dạng thực tế được dùng nội bộ. Để truy vấn các định dạng mà một thiết bị hỗ trợ dựa trên CameraCharacteristics, hãy dùng mã sau:
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();
Kích thước đầu ra
Tất cả kích thước đầu ra có sẵn đều được liệt kê theo StreamConfigurationMap.getOutputSizes(), nhưng chỉ có 2 kích thước liên quan đến khả năng tương thích: PREVIEW và MAXIMUM. Các kích thước này đóng vai trò là cận trên. Nếu một thứ có kích thước PREVIEW hoạt động, thì mọi thứ có kích thước nhỏ hơn PREVIEW cũng sẽ hoạt động. Điều này cũng đúng đối với MAXIMUM. Tài liệu cho CameraDevice giải thích các kích thước này.
Các kích thước đầu ra có sẵn phụ thuộc vào lựa chọn định dạng. Với CameraCharacteristics và một định dạng, bạn có thể truy vấn các kích thước đầu ra có sẵn như sau:
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);
Trong các trường hợp sử dụng bản xem trước và ghi hình bằng camera, hãy dùng lớp đích để xác định các kích thước được hỗ trợ. Định dạng này sẽ do chính khung camera xử lý:
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);
Để lấy kích thước MAXIMUM, hãy sắp xếp các kích thước đầu ra theo diện tích và trả về kích thước lớn nhất:
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 là kích thước phù hợp nhất với độ phân giải màn hình của thiết bị hoặc với độ phân giải 1080p (1920x1080), tuỳ theo kích thước nào nhỏ hơn. Tỷ lệ khung hình có thể không khớp chính xác với tỷ lệ khung hình của màn hình, vì vậy, bạn có thể cần áp dụng hiệu ứng hòm thư hoặc cắt luồng để hiển thị ở chế độ toàn màn hình. Để có kích thước xem trước phù hợp, hãy so sánh các kích thước đầu ra có sẵn với kích thước màn hình trong khi xem xét rằng màn hình có thể được xoay.
Đoạn mã sau đây xác định một lớp hỗ trợ, SmartSize, giúp việc so sánh kích thước trở nên dễ dàng hơn một chút:
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; }
Kiểm tra cấp phần cứng được hỗ trợ
Để xác định các chức năng có sẵn trong thời gian chạy, hãy kiểm tra cấp độ phần cứng được hỗ trợ bằng cách sử dụng CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL.
Với đối tượng CameraCharacteristics, bạn có thể truy xuất cấp độ phần cứng bằng một câu lệnh duy nhất:
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);
Tổng hợp tất cả các yếu tố
Với loại đầu ra, kích thước đầu ra và cấp độ phần cứng, bạn có thể xác định những tổ hợp luồng nào là hợp lệ. Biểu đồ sau đây là thông tin tổng quan nhanh về các cấu hình được hỗ trợ bởi CameraDevice có cấp phần cứng LEGACY.
| Mục tiêu 1 | Mục tiêu 2 | Mục tiêu 3 | (Các) trường hợp sử dụng mẫu | |||
|---|---|---|---|---|---|---|
| Loại | Kích thước tối đa | Loại | Kích thước tối đa | Loại | Kích thước tối đa | |
PRIV |
MAXIMUM |
Xem trước đơn giản, xử lý video bằng GPU hoặc ghi hình mà không xem trước. | ||||
JPEG |
MAXIMUM |
Chụp ảnh tĩnh mà không cần ống ngắm. | ||||
YUV |
MAXIMUM |
Xử lý video/hình ảnh trong ứng dụng. | ||||
PRIV |
PREVIEW |
JPEG |
MAXIMUM |
Chụp ảnh tĩnh tiêu chuẩn. | ||
YUV |
PREVIEW |
JPEG |
MAXIMUM |
Xử lý trong ứng dụng và chụp ảnh tĩnh. | ||
PRIV |
PREVIEW |
PRIV |
PREVIEW |
Bản ghi âm tiêu chuẩn. | ||
PRIV |
PREVIEW |
YUV |
PREVIEW |
Xem trước và xử lý trong ứng dụng. | ||
PRIV |
PREVIEW |
YUV |
PREVIEW |
JPEG |
MAXIMUM |
Chụp ảnh tĩnh và xử lý trong ứng dụng. |
LEGACY là cấp độ phần cứng thấp nhất có thể. Bảng này cho thấy rằng mọi thiết bị hỗ trợ Camera2 (API cấp 21 trở lên) đều có thể xuất tối đa 3 luồng đồng thời bằng cách sử dụng cấu hình phù hợp và nếu không có quá nhiều chi phí chung làm hạn chế hiệu suất, chẳng hạn như các hạn chế về bộ nhớ, CPU hoặc nhiệt độ.
Ứng dụng của bạn cũng cần định cấu hình các vùng đệm đầu ra nhắm đến. Ví dụ: để nhắm đến một thiết bị có cấp độ phần cứng LEGACY, bạn có thể thiết lập 2 bề mặt đầu ra mục tiêu, một bề mặt dùng ImageFormat.PRIVATE và một bề mặt khác dùng ImageFormat.YUV_420_888. Đây là một tổ hợp được hỗ trợ khi sử dụng kích thước PREVIEW. Để sử dụng hàm được xác định trước đó trong chủ đề này, việc lấy kích thước xem trước bắt buộc cho một mã nhận dạng camera cần có mã sau:
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);
Bạn cần đợi cho đến khi SurfaceView sẵn sàng bằng cách sử dụng các lệnh gọi lại được cung cấp:
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 } ... });
Bạn có thể buộc SurfaceView khớp với kích thước đầu ra của camera bằng cách gọi SurfaceHolder.setFixedSize() hoặc bạn có thể áp dụng phương pháp tương tự như AutoFitSurfaceView từ mô-đun Chung của các mẫu camera trên GitHub. Phương pháp này đặt kích thước tuyệt đối, có tính đến cả tỷ lệ khung hình và không gian có sẵn, đồng thời tự động điều chỉnh khi các thay đổi về hoạt động được kích hoạt.
Việc thiết lập bề mặt khác từ ImageReader với định dạng mong muốn sẽ dễ dàng hơn vì không có lệnh gọi lại nào cần chờ:
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);
Khi sử dụng vùng đệm mục tiêu chặn như ImageReader, hãy loại bỏ các khung hình sau khi sử dụng:
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 cấp phần cứng nhắm đến các thiết bị có mẫu số chung nhỏ nhất. Bạn có thể thêm phân nhánh có điều kiện và sử dụng kích thước RECORD cho một trong các bề mặt đích đầu ra trong những thiết bị có cấp độ phần cứng LIMITED, hoặc thậm chí tăng kích thước này lên MAXIMUM cho những thiết bị có cấp độ phần cứng FULL.