Примечание. На этой странице рассматривается пакет 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 basic 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 basic 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.