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