Примечание. На этой странице рассматривается пакет Camera2. Если вашему приложению не требуются определенные низкоуровневые функции Camera2, рекомендуем использовать CameraX. CameraX и Camera2 поддерживают Android 5.0 (уровень API 21) и более поздние версии.
Приложение камеры может использовать несколько потоков кадров одновременно. В некоторых случаях для разных потоков даже требуется разное разрешение кадра или формат пикселей. Вот несколько примеров:
- Запись видео. Один поток используется для предварительного просмотра, а другой кодируется и сохраняется в файл.
- Сканирование штрихкодов. Один поток для предварительного просмотра, другой – для распознавания штрихкодов.
- Вычислительная фотография. Один поток для предварительного просмотра, другой – для распознавания лиц и сцен.
Обработка кадров требует значительных ресурсов, а при параллельной обработке потоков или конвейеров затраты возрастают.
Такие ресурсы, как ЦП, графический процессор и цифровой сигнальный процессор, могут использовать возможности повторной обработки фреймворка, но такие ресурсы, как память, будут расти линейно.
Несколько целей на запрос
Несколько видео с камер можно объединить в один CameraCaptureRequest.
В следующем фрагменте кода показано, как настроить сеанс камеры с одним потоком для предварительного просмотра и другим – для обработки изображений.
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);
Если вы правильно настроите целевые поверхности, этот код будет создавать только потоки, соответствующие минимальной частоте кадров, определяемой StreamComfigurationMap.GetOutputMinFrameDuration(int, Size) и StreamComfigurationMap.GetOutputStallDuration(int, Size).
Фактическая производительность зависит от устройства, но Android гарантирует поддержку определенных комбинаций в зависимости от трех переменных: типа вывода, размера вывода и уровня оборудования.
Если вы используете неподдерживаемую комбинацию переменных, то частота кадров может быть низкой. Если это не так, то будет активирован один из обратных вызовов при сбое.
В документации по createCaptureSession описано, работа каких функций гарантируется.
Тип документа
Тип вывода – это формат, в котором закодированы кадры. Возможные значения: PRIV, YUV, JPEG и RAW. Они описаны в документации по createCaptureSession.
Если вы хотите обеспечить максимальную совместимость, используйте ImageFormat.YUV_420_888 для анализа кадров и ImageFormat.JPEG для неподвижных изображений. Для предварительного просмотра и записи, скорее всего, будут использоваться следующие классы:
SurfaceView,
TextureView,
MediaRecorder, MediaCodec или
RenderScript.Allocation. В таких случаях не указывайте формат изображения. Для совместимости он будет считаться как ImageFormat.PRIVATE независимо от того, какой формат используется на самом деле. Чтобы запросить форматы, поддерживаемые устройством с определенным значением CameraCharacteristics, используйте следующий код:
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();
Размер выходных данных
Все доступные размеры выходных файлов перечислены в StreamConfigurationMap.getOutputSizes(), но только два из них связаны с совместимостью: PREVIEW и MAXIMUM. Размеры действуют как верхние границы. Если подходит размер PREVIEW, то подойдет и любой размер меньше PREVIEW. То же самое относится к MAXIMUM. Подробные сведения о размерах приведены в документации по CameraDevice.
Доступные размеры выходных файлов зависят от выбранного формата. Если у вас есть CameraCharacteristics и формат, вы можете запросить доступные размеры выходных данных следующим образом:
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);
В случаях предварительного просмотра и записи с камеры используйте целевой класс, чтобы определить поддерживаемые размеры. Формат будет обрабатываться самим фреймворком камеры:
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);
Чтобы получить размер MAXIMUM, отсортируйте размеры по площади и выберите наибольший:
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 — это разрешение, которое лучше всего подходит для разрешения экрана устройства, или 1080p (1920 x 1080) в зависимости от того, что меньше. Соотношение сторон может не совпадать с соотношением сторон экрана, поэтому вам может потребоваться применить кашетирование или обрезку к потоку, чтобы отобразить его в полноэкранном режиме. Чтобы получить правильный размер предварительного просмотра, сравните доступные размеры вывода с размером дисплея, учитывая, что дисплей может быть повернут.
В приведенном ниже коде определяется вспомогательный класс SmartSize, который упрощает сравнение размеров.
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; }
Проверьте уровень поддержки аппаратного обеспечения
Чтобы определить доступные возможности во время выполнения, проверьте поддерживаемый уровень оборудования с помощью CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL.
С помощью объекта CameraCharacteristics можно получить уровень аппаратного обеспечения, используя одну инструкцию:
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);
Объединение всех частей
Тип и размер выходных данных, а также уровень аппаратного обеспечения позволяют определить, какие комбинации потоков допустимы. На диаграмме ниже показаны конфигурации, поддерживаемые CameraDevice с уровнем аппаратного обеспечения LEGACY.
| Цель 1 | Цель 2 | Цель 3 | Примеры использования | |||
|---|---|---|---|---|---|---|
| Тип | Максимальный размер | Тип | Максимальный размер | Тип | Максимальный размер | |
PRIV |
MAXIMUM |
Простой предварительный просмотр, обработка видео графическим процессором или запись видео без предварительного просмотра. | ||||
JPEG |
MAXIMUM |
Фотосъемка без видоискателя. | ||||
YUV |
MAXIMUM |
Обработка видео и изображений в приложении. | ||||
PRIV |
PREVIEW |
JPEG |
MAXIMUM |
Стандартная фотосъемка. | ||
YUV |
PREVIEW |
JPEG |
MAXIMUM |
Обработка в приложении и съемка фотографий. | ||
PRIV |
PREVIEW |
PRIV |
PREVIEW |
Стандартная запись. | ||
PRIV |
PREVIEW |
YUV |
PREVIEW |
Предварительная версия и обработка в приложении. | ||
PRIV |
PREVIEW |
YUV |
PREVIEW |
JPEG |
MAXIMUM |
Фотосъемка и обработка в приложении. |
LEGACY – самый низкий уровень аппаратного обеспечения. Из таблицы видно, что каждое устройство, поддерживающее Camera2 (уровень API 21 и выше), может выводить до трех одновременных потоков при правильной конфигурации и отсутствии слишком больших накладных расходов, ограничивающих производительность, таких как ограничения памяти, ЦП или температуры.
В приложении также необходимо настроить целевые выходные буферы. Например, чтобы настроить таргетинг на устройство с аппаратным уровнем LEGACY, можно задать две целевые области вывода: одну с использованием ImageFormat.PRIVATE, а другую – с использованием ImageFormat.YUV_420_888. Это поддерживаемое сочетание при использовании размера PREVIEW. Чтобы получить нужные размеры предварительного просмотра для идентификатора камеры, используя функцию, определенную ранее в этой теме, требуется следующий код:
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);
Для этого нужно дождаться готовности SurfaceView, используя предоставленные функции обратного вызова:
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 } ... });
Вы можете принудительно задать для SurfaceView размер, соответствующий размеру выходного изображения камеры, вызвав метод SurfaceHolder.setFixedSize(). Также можно использовать подход, аналогичный AutoFitSurfaceView из общего модуля примеров камеры на GitHub. Он задает абсолютный размер с учетом соотношения сторон и доступного пространства, а также автоматически корректирует его при изменении активности.
Настроить другую платформу из ImageReader с нужным форматом проще, поскольку не нужно ждать обратных вызовов.
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);
Если вы используете блокирующий целевой буфер, например ImageReader, удаляйте кадры после их использования:
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 предназначен для устройств с наименьшим общим знаменателем. Вы можете добавить условное ветвление и использовать размер RECORD для одной из целевых поверхностей вывода на устройствах с уровнем аппаратной поддержки LIMITED или даже увеличить его до MAXIMUM для устройств с уровнем аппаратной поддержки FULL.