Remarque:Cette page fait référence au package Camera2. Nous vous recommandons d'utiliser CameraX, sauf si votre application nécessite des fonctionnalités spécifiques de base de Camera2. CameraX et Camera2 sont compatibles avec Android 5.0 (niveau d'API 21) ou version ultérieure.
Une application d'appareil photo peut utiliser plusieurs flux d'images simultanément. Dans Dans certains cas, différents flux requièrent même une résolution d'image ou un pixel . Voici certains cas d'utilisation types :
- Enregistrement vidéo: un flux pour la prévisualisation, un autre en cours d'encodage et de sauvegarde dans un fichier.
- Lecture de codes-barres: un flux pour l'aperçu et un autre pour la détection des codes-barres.
- Photographie computationnelle: un flux pour l'aperçu, un autre pour les visages/scènes la détection automatique.
Le traitement des trames a un coût de performances non négligeable, lors du traitement par flux ou en pipeline.
Des ressources telles que le CPU, le GPU et le DSP peuvent être en mesure de tirer parti du le retraitement du framework mais les ressources telles que la mémoire augmenteront de façon linéaire.
Plusieurs cibles par demande
Vous pouvez combiner plusieurs flux de caméra en un seul
CameraCaptureRequest
L'extrait de code suivant montre comment configurer une session d'appareil photo avec
pour l'aperçu de l'appareil photo et un autre pour le traitement de l'image:
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);
Si vous configurez correctement les surfaces cibles, ce code ne produira que
qui atteignent le FPS minimal déterminé par
StreamComfigurationMap.GetOutputMinFrameDuration(int, Size)
et
StreamComfigurationMap.GetOutputStallDuration(int, Size)
Les performances réelles varient d'un appareil à l'autre, même si Android fournit certaines
La garantie de prendre en charge des combinaisons spécifiques en fonction de trois variables:
type de sortie, taille de la sortie et niveau matériel.
L'utilisation d'une combinaison de variables non prise en charge peut fonctionner à une fréquence d'images faible. si
il ne déclenche pas l'un des rappels d'échec.
La documentation pour createCaptureSession
décrit ce dont le fonctionnement
est garanti.
Type de sortie
Le type de sortie désigne le format dans lequel les images sont encodées. La
les valeurs possibles sont PRIV, YUV, JPEG et RAW. La documentation
createCaptureSession
les décrivent.
Lorsque vous choisissez le type de sortie de votre application, si l'objectif est de maximiser
la compatibilité, puis utilisez
ImageFormat.YUV_420_888
pour l'analyse de trame et
ImageFormat.JPEG
pour les images fixes
images. Pour les scénarios de prévisualisation et d'enregistrement, vous utiliserez probablement
SurfaceView
TextureView
MediaRecorder
MediaCodec
ou
RenderScript.Allocation
Dans
ne spécifiez pas de format d'image. Pour des raisons de compatibilité, elles sont comptabilisées comme
ImageFormat.PRIVATE
quel que soit le format réel
utilisé en interne. Pour interroger les formats compatibles
par un appareil compte tenu de
CameraCharacteristics
utilisez le code suivant:
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();
Taille de sortie
Toutes les tailles de sortie disponibles sont répertoriées par
StreamConfigurationMap.getOutputSizes()
mais seuls deux sont liés à la compatibilité: PREVIEW
et MAXIMUM
. Les tailles
servent de limites supérieures. Si un élément de taille PREVIEW
fonctionne, alors tout élément avec
une taille inférieure à PREVIEW
fonctionnera également. Il en va de même pour MAXIMUM
. La
documentation pour
CameraDevice
explique ces tailles.
Les tailles de sortie disponibles dépendent du format choisi. Étant donné le
CameraCharacteristics
et un format, vous pouvez interroger les tailles de sortie disponibles comme ceci:
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);
Dans les cas d'utilisation d'aperçu et d'enregistrement de l'appareil photo, utilisez la classe cible pour déterminer tailles compatibles. Le format sera géré par le framework de l'appareil photo lui-même:
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);
Pour obtenir la taille MAXIMUM
, triez les tailles de sortie par zone et renvoyez la plus grande
une:
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
désigne la taille qui correspond le mieux à la résolution d'écran de l'appareil
1080p (1 920 x 1 080), selon la valeur la plus petite. Il est possible que le format ne corresponde pas à
de l'écran. Vous devrez donc peut-être appliquer le format letterbox ou
recadrez l'image dans le flux
pour l'afficher en mode plein écran. Pour bien comprendre
la taille d'aperçu, comparez les tailles de sortie disponibles avec la taille d'affichage
en tenant compte du fait que l'écran peut pivoter.
Le code suivant définit une classe d'assistance, SmartSize
, qui rendra la taille
des comparaisons:
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; }
Vérifier le niveau de matériel compatible
Pour déterminer les capacités disponibles au moment de l'exécution, vérifiez le matériel compatible
à l'aide de
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL
Avec un
CameraCharacteristics
vous pouvez récupérer le niveau de matériel à l'aide d'une seule instruction:
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);
Assembler toutes les pièces
Avec le type de sortie, la taille de la sortie et le niveau matériel, vous pouvez déterminer
combinaisons de flux sont valides. Le graphique suivant est un instantané
configurations compatibles avec un CameraDevice
avec
LEGACY
au niveau matériel.
Cible 1 | Cible 2 | Cible 3 | Exemples de cas d'utilisation | |||
---|---|---|---|---|---|---|
Type | Taille maximale | Type | Taille maximale | Type | Taille maximale | |
PRIV |
MAXIMUM |
Aperçu simple, traitement vidéo par GPU ou enregistrement vidéo sans aperçu. | ||||
JPEG |
MAXIMUM |
Capture d'image fixe sans viseur. | ||||
YUV |
MAXIMUM |
Traitement des images et des vidéos dans l'application | ||||
PRIV |
PREVIEW |
JPEG |
MAXIMUM |
Imagerie fixe standard. | ||
YUV |
PREVIEW |
JPEG |
MAXIMUM |
Traitement dans l'application et capture d'images fixes. | ||
PRIV |
PREVIEW |
PRIV |
PREVIEW |
Enregistrement standard. | ||
PRIV |
PREVIEW |
YUV |
PREVIEW |
Aperçu et traitement dans l'application. | ||
PRIV |
PREVIEW |
YUV |
PREVIEW |
Aperçu et traitement dans l'application. | ||
PRIV |
PREVIEW |
YUV |
PREVIEW |
JPEG |
MAXIMUM |
Toujours capturer et traiter les données dans l'application. |
LEGACY
est le niveau de matériel le plus bas possible. Ce tableau montre que chaque
les appareils compatibles avec Camera2 (niveau d'API 21 ou supérieur) peuvent générer jusqu'à trois
des flux simultanés en utilisant la bonne configuration et s'il n'y a pas trop
limitant les performances, comme la mémoire,
le processeur ou les contraintes thermiques.
Votre application doit également configurer le ciblage des tampons de sortie. Par exemple, pour
cibler un appareil avec le niveau de matériel LEGACY
, vous pouvez configurer deux sorties cibles
surfaces, l'une utilisant ImageFormat.PRIVATE
et l'autre utilisant
ImageFormat.YUV_420_888
Cette combinaison est acceptée lorsque vous utilisez
Taille de PREVIEW
. Utilisez la fonction définie précédemment dans cet article pour obtenir
les tailles d'aperçu requises pour un ID d'appareil photo nécessitent le code suivant:
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);
Vous devez attendre que SurfaceView
soit prêt à l'aide des rappels fournis:
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 } ... });
Vous pouvez forcer SurfaceView
à s'adapter à la taille de sortie de la caméra en appelant
SurfaceHolder.setFixedSize()
ou vous pouvez adopter
une approche similaire à
AutoFitSurfaceView
dans le module Common
ce module
des échantillons de caméras sur GitHub, qui définit une taille absolue, en tenant compte
tenir compte à la fois du format et de l'espace disponible, tandis que
lorsque des modifications d'activité sont déclenchées.
Configurer l'autre surface à partir de
ImageReader
au format souhaité est
plus simple, car il n'y a aucun rappel à attendre:
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);
Lorsque vous utilisez un tampon cible bloquant comme ImageReader
, supprimez les frames après
les utilisent:
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);
Le niveau matériel LEGACY
cible les appareils ayant le plus petit dénominateur commun. Vous pouvez
Ajout d'embranchements conditionnels et utilisation de la taille RECORD
pour l'une des cibles de sortie
surfaces des appareils avec le niveau de matériel LIMITED
, ou même l'augmenter à
Taille de MAXIMUM
pour les appareils avec le niveau de matériel FULL
.