Примечание. Эта страница относится к пакету Camera2 . Если вашему приложению не требуются специальные низкоуровневые функции Camera2, мы рекомендуем использовать CameraX . И CameraX, и Camera2 поддерживают Android 5.0 (уровень API 21) и выше.
Приложение камеры может использовать более одного потока кадров одновременно. В некоторых случаях разные потоки даже требуют разного разрешения кадра или формата пикселей. Некоторые типичные случаи использования включают в себя:
- Запись видео : один поток для предварительного просмотра, другой кодируется и сохраняется в файл.
- Сканирование штрих-кода : один поток для предварительного просмотра, другой для обнаружения штрих-кода.
- Вычислительная фотография : один поток для предварительного просмотра, другой для обнаружения лиц/сцен.
При обработке кадров возникают нетривиальные затраты на производительность, и эти затраты умножаются при параллельной потоковой или конвейерной обработке.
Такие ресурсы, как ЦП, графический процессор и DSP, возможно, смогут воспользоваться возможностями повторной обработки платформы, но такие ресурсы, как память, будут расти линейно.
Несколько целей на запрос
Несколько потоков камер можно объединить в один CameraCaptureRequest
. В следующем фрагменте кода показано, как настроить сеанс камеры с одним потоком для предварительного просмотра камеры и другим потоком для обработки изображений:
Котлин
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)
Ява
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);
Если вы правильно настроите целевые поверхности, этот код будет создавать только потоки, соответствующие минимальному FPS, определенному 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
, используйте следующий код:
Котлин
val characteristics: CameraCharacteristics = ... val supportedFormats = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP).outputFormats
Ява
CameraCharacteristics characteristics = …; int[] supportedFormats = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP).getOutputFormats();
Выходной размер
Все доступные выходные размеры перечислены в StreamConfigurationMap.getOutputSizes()
, но только два связаны с совместимостью: PREVIEW
и MAXIMUM
. Размеры действуют как верхние границы. Если что-то размером PREVIEW
работает, то и все, что меньше PREVIEW
тоже будет работать. То же самое верно и для MAXIMUM
. В документации CameraDevice
описаны эти размеры.
Доступные выходные размеры зависят от выбора формата. Учитывая CameraCharacteristics
и формат, вы можете запросить доступные выходные размеры следующим образом:
Котлин
val characteristics: CameraCharacteristics = ... val outputFormat: Int = ... // such as ImageFormat.JPEG val sizes = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) .getOutputSizes(outputFormat)
Ява
CameraCharacteristics characteristics = …; int outputFormat = …; // such as ImageFormat.JPEG Size[] sizes = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) .getOutputSizes(outputFormat);
В вариантах использования предварительного просмотра камеры и записи используйте целевой класс, чтобы определить поддерживаемые размеры. Формат будет обрабатываться самой инфраструктурой камеры:
Котлин
val characteristics: CameraCharacteristics = ... val targetClass: Class <T> = ... // such as SurfaceView::class.java val sizes = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) .getOutputSizes(targetClass)
Ява
CameraCharacteristics characteristics = …; int outputFormat = …; // such as ImageFormat.JPEG Size[] sizes = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) .getOutputSizes(outputFormat);
Чтобы получить MAXIMUM
размер, отсортируйте выходные размеры по площади и верните самый большой:
Котлин
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 } }
Ява
@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 (1920x1080), в зависимости от того, что меньше. Соотношение сторон может не точно соответствовать соотношению сторон экрана, поэтому вам может потребоваться применить к потоку форматирование или обрезку, чтобы отобразить его в полноэкранном режиме. Чтобы получить правильный размер предварительного просмотра, сравните доступные выходные размеры с размером дисплея, принимая во внимание, что дисплей можно поворачивать.
Следующий код определяет вспомогательный класс SmartSize
, который немного упростит сравнение размеров:
Котлин
/** 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 }
Ява
/** 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
вы можете получить уровень оборудования с помощью одного оператора:
Котлин
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)
Ява
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 | Предварительный просмотр и обработка в приложении. | ||
PRIV | PREVIEW | YUV | PREVIEW | JPEG | MAXIMUM | По-прежнему захват плюс обработка в приложении. |
LEGACY
— это самый низкий возможный уровень аппаратного обеспечения. В этой таблице показано, что каждое устройство, поддерживающее Camera2 (уровень API 21 и выше), может выводить до трех одновременных потоков при использовании правильной конфигурации и при отсутствии слишком больших накладных расходов, ограничивающих производительность, таких как память, процессор или температурные ограничения.
Вашему приложению также необходимо настроить целевые выходные буферы. Например, чтобы настроить устройство с аппаратным уровнем LEGACY
, вы можете настроить две целевые поверхности вывода: одну с помощью ImageFormat.PRIVATE
, а другую с помощью ImageFormat.YUV_420_888
. Это поддерживаемая комбинация при использовании размера PREVIEW
. Используя функцию, определенную ранее в этом разделе, для получения необходимых размеров предварительного просмотра для идентификатора камеры требуется следующий код:
Котлин
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)
Ява
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
будет готов, используя предоставленные обратные вызовы:
Котлин
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 } ... })
Ява
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
с нужным форматом проще, поскольку не нужно ждать обратных вызовов:
Котлин
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)
Ява
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
, отбрасывайте кадры после их использования:
Котлин
imageReader.setOnImageAvailableListener({ val frame = it.acquireNextImage() // Do something with "frame" here it.close() }, null)
Ява
imageReader.setOnImageAvailableListener(listener -> { Image frame = listener.acquireNextImage(); // Do something with "frame" here listener.close(); }, null);
Уровень аппаратного обеспечения LEGACY
нацелен на устройства с наименьшим общим знаменателем. Вы можете добавить условное ветвление и использовать размер RECORD
для одной из целевых поверхностей вывода в устройствах с LIMITED
уровнем аппаратного обеспечения или даже увеличить его до MAXIMUM
размера для устройств с FULL
уровнем аппаратного обеспечения.