Contrôler la caméra

Au cours de cette leçon, nous allons voir comment contrôler la partie matérielle d'un appareil photo directement à l'aide des API du framework.

Remarque : La classe Camera, qui est mentionnée sur cette page, est obsolète. Nous vous recommandons d'utiliser CameraX ou, dans des cas d'utilisation spécifiques, Camera2. CameraX et Camera2 sont compatibles avec Android 5.0 (niveau d'API 21) ou version ultérieure.

Contrôler directement l'appareil photo d'un appareil nécessite beaucoup plus de codes que de demander des photos ou vidéos aux applications dédiées existantes. Toutefois, si vous voulez créer une application Appareil photo spécialisée ou une solution entièrement intégrée dans l'interface utilisateur de votre application, cette leçon vous montre comment procéder.

Consultez les ressources associées suivantes :

Ouvrir l'objet Camera

La première étape pour contrôler directement l'appareil photo consiste à obtenir une instance de l'objet Camera. Comme le fait la propre application Appareil photo d'Android, nous vous recommandons d'accéder à l'appareil photo en ouvrant Camera sur un thread distinct, lancé à partir de onCreate(). Cette approche est judicieuse, car la procédure peut prendre un certain temps et bloquer le thread UI. Dans une implémentation plus basique, l'ouverture de l'objet Camera peut être reportée à la méthode onResume() pour faciliter la réutilisation du code et simplifier le flux de contrôle.

L'appel de Camera.open() génère une exception si l'appareil photo est déjà utilisé par une autre application. C'est pourquoi nous l'encapsulons dans un bloc try.

Kotlin

private fun safeCameraOpen(id: Int): Boolean {
    return try {
        releaseCameraAndPreview()
        mCamera = Camera.open(id)
        true
    } catch (e: Exception) {
        Log.e(getString(R.string.app_name), "failed to open Camera")
        e.printStackTrace()
        false
    }
}

private fun releaseCameraAndPreview() {
    preview?.setCamera(null)
    mCamera?.also { camera ->
        camera.release()
        mCamera = null
    }
}

Java

private boolean safeCameraOpen(int id) {
    boolean qOpened = false;

    try {
        releaseCameraAndPreview();
        camera = Camera.open(id);
        qOpened = (camera != null);
    } catch (Exception e) {
        Log.e(getString(R.string.app_name), "failed to open Camera");
        e.printStackTrace();
    }

    return qOpened;
}

private void releaseCameraAndPreview() {
    preview.setCamera(null);
    if (camera != null) {
        camera.release();
        camera = null;
    }
}

Depuis le niveau d'API 9, le framework d'appareil photo est compatible avec plusieurs caméras. Si vous utilisez l'ancienne API et appelez open() sans argument, vous obtenez la première caméra arrière.

Créer l'aperçu de l'appareil photo

Pour prendre une photo, l'utilisateur doit généralement prévisualiser le sujet avant d'appuyer sur l'obturateur. Pour cela, vous pouvez utiliser un SurfaceView pour afficher un aperçu de ce que le capteur photo détecte.

Classe Preview

Pour afficher un aperçu, vous avez d'abord besoin d'une classe Preview. L'aperçu nécessite d'implémenter l'interface android.view.SurfaceHolder.Callback, qui permet de transmettre à l'application les données d'image provenant de la partie matérielle de l'appareil photo.

Kotlin

class Preview(
        context: Context,
        val surfaceView: SurfaceView = SurfaceView(context)
) : ViewGroup(context), SurfaceHolder.Callback {

    var mHolder: SurfaceHolder = surfaceView.holder.apply {
        addCallback(this@Preview)
        setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS)
    }
    ...
}

Java

class Preview extends ViewGroup implements SurfaceHolder.Callback {

    SurfaceView surfaceView;
    SurfaceHolder holder;

    Preview(Context context) {
        super(context);

        surfaceView = new SurfaceView(context);
        addView(surfaceView);

        // Install a SurfaceHolder.Callback so we get notified when the
        // underlying surface is created and destroyed.
        holder = surfaceView.getHolder();
        holder.addCallback(this);
        holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    }
...
}

Comme indiqué dans la prochaine section, la classe Preview doit être transmise à l'objet Camera avant que l'aperçu de l'image en direct puisse être lancé.

Configurer et lancer l'aperçu

Vous devez créer une instance d'appareil photo et l'aperçu associé dans un ordre précis, en plaçant l'objet Camera en premier. Dans l'extrait ci-dessous, le processus d'initialisation de l'appareil photo est encapsulé de sorte que Camera.startPreview() est appelé par la méthode setCamera() chaque fois que l'utilisateur apporte une modification à l'appareil photo. L'aperçu doit également être relancé dans la méthode de rappel surfaceChanged() de la classe Preview.

Kotlin

fun setCamera(camera: Camera?) {
    if (mCamera == camera) {
        return
    }

    stopPreviewAndFreeCamera()

    mCamera = camera

    mCamera?.apply {
        mSupportedPreviewSizes = parameters.supportedPreviewSizes
        requestLayout()

        try {
            setPreviewDisplay(holder)
        } catch (e: IOException) {
            e.printStackTrace()
        }

        // Important: Call startPreview() to start updating the preview
        // surface. Preview must be started before you can take a picture.
        startPreview()
    }
}

Java

public void setCamera(Camera camera) {
    if (mCamera == camera) { return; }

    stopPreviewAndFreeCamera();

    mCamera = camera;

    if (mCamera != null) {
        List<Size> localSizes = mCamera.getParameters().getSupportedPreviewSizes();
        supportedPreviewSizes = localSizes;
        requestLayout();

        try {
            mCamera.setPreviewDisplay(holder);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // Important: Call startPreview() to start updating the preview
        // surface. Preview must be started before you can take a picture.
        mCamera.startPreview();
    }
}

Modifier les paramètres de l'appareil photo

Les paramètres de l'appareil photo changent la façon dont les photos sont prises (cela va du niveau de zoom jusqu'à la correction d'exposition). Dans cet exemple, seule la taille de l'aperçu change. Pour en savoir plus, consultez le code source de l'application Appareil photo.

Kotlin

override fun surfaceChanged(holder: SurfaceHolder, format: Int, w: Int, h: Int) {
    mCamera?.apply {
        // Now that the size is known, set up the camera parameters and begin
        // the preview.
        parameters?.also { params ->
            params.setPreviewSize(previewSize.width, previewSize.height)
            requestLayout()
            parameters = params
        }

        // Important: Call startPreview() to start updating the preview surface.
        // Preview must be started before you can take a picture.
        startPreview()
    }
}

Java

@Override
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
    // Now that the size is known, set up the camera parameters and begin
    // the preview.
    Camera.Parameters parameters = mCamera.getParameters();
    parameters.setPreviewSize(previewSize.width, previewSize.height);
    requestLayout();
    mCamera.setParameters(parameters);

    // Important: Call startPreview() to start updating the preview surface.
    // Preview must be started before you can take a picture.
    mCamera.startPreview();
}

Définir l'orientation de l'aperçu

La plupart des applications Appareil photo verrouillent l'affichage en mode Paysage, car c'est l'orientation naturelle du capteur. Sachez que ce paramètre ne vous empêche pas de prendre des photos en mode Portrait, car l'orientation de l'appareil est enregistrée dans l'en-tête EXIF. La méthode setCameraDisplayOrientation() vous permet de modifier comment l'aperçu est affiché sans affecter la façon dont l'image est enregistrée. Toutefois, avant le niveau d'API 14 sous Android, vous devez arrêter l'aperçu avant de changer l'orientation, puis le relancer.

Prendre une photo

Pour prendre une photo une fois l'aperçu lancé, utilisez la méthode Camera.takePicture(). Vous pouvez créer des objets Camera.PictureCallback et Camera.ShutterCallback, puis les transmettre dans Camera.takePicture().

Si vous voulez saisir des images en continu, vous pouvez créer un Camera.PreviewCallback qui implémente onPreviewFrame(). Pour quelque chose d'intermédiaire, vous pouvez ne capturer que les frames d'aperçu sélectionnés ou configurer une action différée pour appeler takePicture().

Relancer l'aperçu

Une fois qu'une photo est prise, vous devez relancer l'aperçu avant que l'utilisateur puisse en prendre une autre. Dans cet exemple, l'aperçu est relancé en surchargeant le bouton de l'obturateur.

Kotlin

fun onClick(v: View) {
    previewState = if (previewState == K_STATE_FROZEN) {
        camera?.startPreview()
        K_STATE_PREVIEW
    } else {
        camera?.takePicture(null, rawCallback, null)
        K_STATE_BUSY
    }
    shutterBtnConfig()
}

Java

@Override
public void onClick(View v) {
    switch(previewState) {
    case K_STATE_FROZEN:
        camera.startPreview();
        previewState = K_STATE_PREVIEW;
        break;

    default:
        camera.takePicture( null, rawCallback, null);
        previewState = K_STATE_BUSY;
    } // switch
    shutterBtnConfig();
}

Arrêter l'aperçu et libérer l'objet Camera

Une fois que votre application a terminé d'utiliser l'appareil photo, il est temps de "nettoyer". Plus précisément, vous devez libérer l'objet Camera sous peine de faire planter d'autres applications, y compris de nouvelles instances de votre propre application.

Quand devez-vous arrêter l'aperçu et libérer l'objet Camera ? La destruction de la surface d'aperçu est un signe qu'il est temps d'arrêter l'aperçu et de libérer l'objet Camera, comme indiqué dans ces méthodes de la classe Preview.

Kotlin

override fun surfaceDestroyed(holder: SurfaceHolder) {
    // Surface will be destroyed when we return, so stop the preview.
    // Call stopPreview() to stop updating the preview surface.
    mCamera?.stopPreview()
}

/**
 * When this function returns, mCamera will be null.
 */
private fun stopPreviewAndFreeCamera() {
    mCamera?.apply {
        // Call stopPreview() to stop updating the preview surface.
        stopPreview()

        // Important: Call release() to release the camera for use by other
        // applications. Applications should release the camera immediately
        // during onPause() and re-open() it during onResume()).
        release()

        mCamera = null
    }
}

Java

@Override
public void surfaceDestroyed(SurfaceHolder holder) {
    // Surface will be destroyed when we return, so stop the preview.
    if (mCamera != null) {
        // Call stopPreview() to stop updating the preview surface.
        mCamera.stopPreview();
    }
}

/**
 * When this function returns, mCamera will be null.
 */
private void stopPreviewAndFreeCamera() {

    if (mCamera != null) {
        // Call stopPreview() to stop updating the preview surface.
        mCamera.stopPreview();

        // Important: Call release() to release the camera for use by other
        // applications. Applications should release the camera immediately
        // during onPause() and re-open() it during onResume()).
        mCamera.release();

        mCamera = null;
    }
}

Plutôt tôt dans la leçon, cette procédure faisait également partie de la méthode setCamera(). C'est pourquoi l'initialisation d'un appareil photo commence toujours par l'arrêt de l'aperçu.