Android
Camera 2 API
Recherche…
Paramètres
Paramètre | Détails |
---|---|
CameraCaptureSession | Une session de capture configurée pour un CameraDevice , utilisée pour capturer des images de la caméra ou pour retraiter des images capturées depuis la caméra au cours de la même session. |
CameraDevice | Une représentation d'une seule caméra connectée à un appareil Android |
CameraCharacteristics | Les propriétés décrivant un CameraDevice. Ces propriétés sont corrigées pour un CameraDevice donné et peuvent être interrogées via l'interface getCameraCharacteristics(String) avec getCameraCharacteristics(String) |
CameraManager | Un gestionnaire de services système pour détecter, caractériser et se connecter à CameraDevices . Vous pouvez obtenir une instance de cette classe en appelant Context.getSystemService() |
CaptureRequest | Un ensemble immuable de paramètres et de sorties nécessaires pour capturer une seule image à partir de l'appareil photo. Contient la configuration du matériel de capture (capteur, objectif, flash), le pipeline de traitement, les algorithmes de contrôle et les tampons de sortie. Contient également la liste des surfaces cibles auxquelles envoyer les données d'image pour cette capture. Peut être créé en utilisant une instance de CaptureRequest.Builder , obtenue en appelant createCaptureRequest(int) |
CaptureResult | Le sous-ensemble des résultats d'une capture d'image unique provenant du capteur d'image. Contient un sous-ensemble de la configuration finale du matériel de capture (capteur, objectif, flash), du pipeline de traitement, des algorithmes de contrôle et des tampons de sortie. Il est produit par un CameraDevice après avoir traité un CaptureRequest |
Remarques
- Les API Camera2 sont disponibles dans API 21+ (Lollipop et au-delà)
- Même si un appareil Android a officiellement une ROM de 21+, il n'y a aucune garantie qu'il implémente les API Camera2, il appartient totalement au fabricant de l'implémenter ou non (Exemple: LG G2 a un support officiel de Lollipop, mais pas d'API Camera2)
- Avec Camera2, Camera ("Camera1") est obsolète
- Avec une grande puissance, vous avez une grande responsabilité: il est plus facile de la gâcher lorsque vous utilisez ces API.
- Rappelez-vous que si vous voulez seulement prendre une photo dans votre application et l'obtenir simplement, vous n'avez pas besoin d'implémenter Camera2, vous pouvez ouvrir l'application appareil photo via un Intent et la recevoir.
Prévisualiser la caméra principale dans un TextureView
Dans ce cas, la création par rapport à l'API 23, les autorisations sont également gérées.
Vous devez ajouter dans le manifeste l'autorisation suivante (quel que soit le niveau de l'API utilisé):
<uses-permission android:name="android.permission.CAMERA"/>
Nous allons créer une activité (Camera2Activity.java) qui remplit un TextureView
avec l'aperçu de la caméra de l'appareil.
L'activité que nous allons utiliser est un AppCompatActivity typique:
public class Camera2Activity extends AppCompatActivity {
Attributs (Vous devrez peut-être lire l'exemple complet pour en comprendre certains)
Le MAX_PREVIEW_SIZE
garanti par l'API Camera2 est 1920x1080
private static final int MAX_PREVIEW_WIDTH = 1920;
private static final int MAX_PREVIEW_HEIGHT = 1080;
TextureView.SurfaceTextureListener
gère plusieurs événements de cycle de vie sur une TextureView
. Dans ce cas, nous écoutons ces événements. Lorsque SurfaceTexture est prêt, nous initialisons la caméra. Lorsque la taille change, nous configurons l'aperçu provenant de la caméra en conséquence
private final TextureView.SurfaceTextureListener mSurfaceTextureListener
= new TextureView.SurfaceTextureListener() {
@Override
public void onSurfaceTextureAvailable(SurfaceTexture texture, int width, int height) {
openCamera(width, height);
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture texture, int width, int height) {
configureTransform(width, height);
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture texture) {
return true;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture texture) {
}
};
Un CameraDevice
représente la caméra d'un périphérique physique. Dans cet attribut, nous sauvegardons l'ID du CameraDevice
actuel
private String mCameraId;
C'est la vue ( TextureView
) que nous utiliserons pour "dessiner" l'aperçu de la caméra.
private TextureView mTextureView;
Le CameraCaptureSession
pour la prévisualisation de la caméra
private CameraCaptureSession mCaptureSession;
Une référence au CameraDevice
ouvert
private CameraDevice mCameraDevice;
La Size
de l'aperçu de la caméra.
private Size mPreviewSize;
CameraDevice.StateCallback
est appelé lorsque CameraDevice
change d'état
private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {
@Override
public void onOpened(@NonNull CameraDevice cameraDevice) {
// This method is called when the camera is opened. We start camera preview here.
mCameraOpenCloseLock.release();
mCameraDevice = cameraDevice;
createCameraPreviewSession();
}
@Override
public void onDisconnected(@NonNull CameraDevice cameraDevice) {
mCameraOpenCloseLock.release();
cameraDevice.close();
mCameraDevice = null;
}
@Override
public void onError(@NonNull CameraDevice cameraDevice, int error) {
mCameraOpenCloseLock.release();
cameraDevice.close();
mCameraDevice = null;
finish();
}
};
Un thread supplémentaire pour l'exécution de tâches qui ne doivent pas bloquer l'interface utilisateur
private HandlerThread mBackgroundThread;
Un Handler
pour exécuter des tâches en arrière-plan
private Handler mBackgroundHandler;
Un ImageReader
qui gère la capture d'images fixes
private ImageReader mImageReader;
CaptureRequest.Builder
pour l'aperçu de la caméra
private CaptureRequest.Builder mPreviewRequestBuilder;
CaptureRequest
généré par mPreviewRequestBuilder
private CaptureRequest mPreviewRequest;
Un Semaphore
pour empêcher l'application de quitter avant de fermer la caméra.
private Semaphore mCameraOpenCloseLock = new Semaphore(1);
Identifiant constant de la demande d'autorisation
private static final int REQUEST_CAMERA_PERMISSION = 1;
Méthodes de cycle de vie Android
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_camera2);
mTextureView = (TextureView) findViewById(R.id.texture);
}
@Override
public void onResume() {
super.onResume();
startBackgroundThread();
// When the screen is turned off and turned back on, the SurfaceTexture is already
// available, and "onSurfaceTextureAvailable" will not be called. In that case, we can open
// a camera and start preview from here (otherwise, we wait until the surface is ready in
// the SurfaceTextureListener).
if (mTextureView.isAvailable()) {
openCamera(mTextureView.getWidth(), mTextureView.getHeight());
} else {
mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
}
}
@Override
public void onPause() {
closeCamera();
stopBackgroundThread();
super.onPause();
}
Méthodes liées à Camera2
Ce sont des méthodes qui utilisent les API Camera2
private void openCamera(int width, int height) {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED) {
requestCameraPermission();
return;
}
setUpCameraOutputs(width, height);
configureTransform(width, height);
CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
try {
if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
throw new RuntimeException("Time out waiting to lock camera opening.");
}
manager.openCamera(mCameraId, mStateCallback, mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
} catch (InterruptedException e) {
throw new RuntimeException("Interrupted while trying to lock camera opening.", e);
}
}
Ferme la caméra actuelle
private void closeCamera() {
try {
mCameraOpenCloseLock.acquire();
if (null != mCaptureSession) {
mCaptureSession.close();
mCaptureSession = null;
}
if (null != mCameraDevice) {
mCameraDevice.close();
mCameraDevice = null;
}
if (null != mImageReader) {
mImageReader.close();
mImageReader = null;
}
} catch (InterruptedException e) {
throw new RuntimeException("Interrupted while trying to lock camera closing.", e);
} finally {
mCameraOpenCloseLock.release();
}
}
Configure les variables de membre liées à la caméra
private void setUpCameraOutputs(int width, int height) {
CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
try {
for (String cameraId : manager.getCameraIdList()) {
CameraCharacteristics characteristics
= manager.getCameraCharacteristics(cameraId);
// We don't use a front facing camera in this sample.
Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING);
if (facing != null && facing == CameraCharacteristics.LENS_FACING_FRONT) {
continue;
}
StreamConfigurationMap map = characteristics.get(
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
if (map == null) {
continue;
}
// For still image captures, we use the largest available size.
Size largest = Collections.max(
Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)),
new CompareSizesByArea());
mImageReader = ImageReader.newInstance(largest.getWidth(), largest.getHeight(),
ImageFormat.JPEG, /*maxImages*/2);
mImageReader.setOnImageAvailableListener(
null, mBackgroundHandler);
Point displaySize = new Point();
getWindowManager().getDefaultDisplay().getSize(displaySize);
int rotatedPreviewWidth = width;
int rotatedPreviewHeight = height;
int maxPreviewWidth = displaySize.x;
int maxPreviewHeight = displaySize.y;
if (maxPreviewWidth > MAX_PREVIEW_WIDTH) {
maxPreviewWidth = MAX_PREVIEW_WIDTH;
}
if (maxPreviewHeight > MAX_PREVIEW_HEIGHT) {
maxPreviewHeight = MAX_PREVIEW_HEIGHT;
}
// Danger! Attempting to use too large a preview size could exceed the camera
// bus' bandwidth limitation, resulting in gorgeous previews but the storage of
// garbage capture data.
mPreviewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class),
rotatedPreviewWidth, rotatedPreviewHeight, maxPreviewWidth,
maxPreviewHeight, largest);
mCameraId = cameraId;
return;
}
} catch (CameraAccessException e) {
e.printStackTrace();
} catch (NullPointerException e) {
// Currently an NPE is thrown when the Camera2API is used but not supported on the
// device this code runs.
Toast.makeText(Camera2Activity.this, "Camera2 API not supported on this device", Toast.LENGTH_LONG).show();
}
}
Crée un nouveau CameraCaptureSession
pour un aperçu de la caméra
private void createCameraPreviewSession() {
try {
SurfaceTexture texture = mTextureView.getSurfaceTexture();
assert texture != null;
// We configure the size of default buffer to be the size of camera preview we want.
texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
// This is the output Surface we need to start preview.
Surface surface = new Surface(texture);
// We set up a CaptureRequest.Builder with the output Surface.
mPreviewRequestBuilder
= mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
mPreviewRequestBuilder.addTarget(surface);
// Here, we create a CameraCaptureSession for camera preview.
mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()),
new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
// The camera is already closed
if (null == mCameraDevice) {
return;
}
// When the session is ready, we start displaying the preview.
mCaptureSession = cameraCaptureSession;
try {
// Auto focus should be continuous for camera preview.
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
// Finally, we start displaying the camera preview.
mPreviewRequest = mPreviewRequestBuilder.build();
mCaptureSession.setRepeatingRequest(mPreviewRequest,
null, mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
@Override
public void onConfigureFailed(
@NonNull CameraCaptureSession cameraCaptureSession) {
showToast("Failed");
}
}, null
);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
Méthodes associées aux autorisations Pour Android API 23+
private void requestCameraPermission() {
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA)) {
new AlertDialog.Builder(Camera2Activity.this)
.setMessage("R string request permission")
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
ActivityCompat.requestPermissions(Camera2Activity.this,
new String[]{Manifest.permission.CAMERA},
REQUEST_CAMERA_PERMISSION);
}
})
.setNegativeButton(android.R.string.cancel,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
finish();
}
})
.create();
} else {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA},
REQUEST_CAMERA_PERMISSION);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
if (requestCode == REQUEST_CAMERA_PERMISSION) {
if (grantResults.length != 1 || grantResults[0] != PackageManager.PERMISSION_GRANTED) {
Toast.makeText(Camera2Activity.this, "ERROR: Camera permissions not granted", Toast.LENGTH_LONG).show();
}
} else {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
Méthodes de thread / gestionnaire d'arrière-plan
private void startBackgroundThread() {
mBackgroundThread = new HandlerThread("CameraBackground");
mBackgroundThread.start();
mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
}
private void stopBackgroundThread() {
mBackgroundThread.quitSafely();
try {
mBackgroundThread.join();
mBackgroundThread = null;
mBackgroundHandler = null;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Méthodes utilitaires
En fonction des choix de Size
pris en charge par une caméra, choisissez la plus petite taille au moins en tant que taille de vue de texture respective, aussi grande que la taille maximale respective et dont les proportions correspondent à la valeur spécifiée. Si n'existe pas, choisissez le plus grand qui est au maximum aussi grand que la taille maximale respective, et dont le rapport hauteur / largeur correspond à la valeur spécifiée
private static Size chooseOptimalSize(Size[] choices, int textureViewWidth,
int textureViewHeight, int maxWidth, int maxHeight, Size aspectRatio) {
// Collect the supported resolutions that are at least as big as the preview Surface
List<Size> bigEnough = new ArrayList<>();
// Collect the supported resolutions that are smaller than the preview Surface
List<Size> notBigEnough = new ArrayList<>();
int w = aspectRatio.getWidth();
int h = aspectRatio.getHeight();
for (Size option : choices) {
if (option.getWidth() <= maxWidth && option.getHeight() <= maxHeight &&
option.getHeight() == option.getWidth() * h / w) {
if (option.getWidth() >= textureViewWidth &&
option.getHeight() >= textureViewHeight) {
bigEnough.add(option);
} else {
notBigEnough.add(option);
}
}
}
// Pick the smallest of those big enough. If there is no one big enough, pick the
// largest of those not big enough.
if (bigEnough.size() > 0) {
return Collections.min(bigEnough, new CompareSizesByArea());
} else if (notBigEnough.size() > 0) {
return Collections.max(notBigEnough, new CompareSizesByArea());
} else {
Log.e("Camera2", "Couldn't find any suitable preview size");
return choices[0];
}
}
Cette méthode configure la transformation de Matrix
mTextureView
en mTextureView
private void configureTransform(int viewWidth, int viewHeight) {
if (null == mTextureView || null == mPreviewSize) {
return;
}
int rotation = getWindowManager().getDefaultDisplay().getRotation();
Matrix matrix = new Matrix();
RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
RectF bufferRect = new RectF(0, 0, mPreviewSize.getHeight(), mPreviewSize.getWidth());
float centerX = viewRect.centerX();
float centerY = viewRect.centerY();
if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) {
bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
float scale = Math.max(
(float) viewHeight / mPreviewSize.getHeight(),
(float) viewWidth / mPreviewSize.getWidth());
matrix.postScale(scale, scale, centerX, centerY);
matrix.postRotate(90 * (rotation - 2), centerX, centerY);
} else if (Surface.ROTATION_180 == rotation) {
matrix.postRotate(180, centerX, centerY);
}
mTextureView.setTransform(matrix);
}
Cette méthode compare deux Size
basées sur leurs zones.
static class CompareSizesByArea implements Comparator<Size> {
@Override
public int compare(Size lhs, Size rhs) {
// We cast here to ensure the multiplications won't overflow
return Long.signum((long) lhs.getWidth() * lhs.getHeight() -
(long) rhs.getWidth() * rhs.getHeight());
}
}
Pas grand chose à voir ici
/**
* Shows a {@link Toast} on the UI thread.
*
* @param text The message to show
*/
private void showToast(final String text) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(Camera2Activity.this, text, Toast.LENGTH_SHORT).show();
}
});
}