Kamera steuern

In dieser Lektion erfahren Sie, wie Sie die Kamerahardware direkt über die Framework-APIs steuern.

Hinweis:Auf dieser Seite wird die Klasse Camera beschrieben, die eingestellt wird. Wir empfehlen die Verwendung von CameraX oder für bestimmte Anwendungsfälle Camera2. Sowohl CameraX als auch Camera2 unterstützen Android 5.0 (API-Level 21) und höher.

Die direkte Steuerung der Kamera eines Geräts erfordert viel mehr Code als das Anfordern von Fotos oder Videos von vorhandenen Kameraanwendungen. Wenn Sie jedoch eine spezielle Kameraanwendung oder etwas entwickeln möchten, das vollständig in die App-Benutzeroberfläche integriert ist, erfahren Sie in dieser Lektion, wie Sie vorgehen.

Weitere Informationen finden Sie unter den folgenden Links:

Kameraobjekt öffnen

Das Abrufen einer Instanz des Camera-Objekts ist der erste Schritt bei der direkten Steuerung der Kamera. Wie bei der Kamera-App von Android wird empfohlen, Camera in einem separaten Thread zu öffnen, der von onCreate() gestartet wird. Dieser Ansatz ist sinnvoll, da es einige Zeit dauern kann und der UI-Thread dadurch überlastet werden könnte. Bei einer einfacheren Implementierung kann das Öffnen der Kamera auf die onResume()-Methode verschoben werden, um die Codewiederverwendung zu erleichtern und die Aufrufabfolge zu vereinfachen.

Wenn Camera.open() aufgerufen wird, wird eine Ausnahme ausgelöst, wenn die Kamera bereits von einer anderen Anwendung verwendet wird. Wir fügen sie daher in einen try-Block ein.

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
    }
}
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;
    }
}

Seit API-Level 9 unterstützt das Kamera-Framework mehrere Kameras. Wenn Sie die alte API verwenden und open() ohne Argument aufrufen, wird die erste Rückkamera zurückgegeben.

Kameravorschau erstellen

Wenn Nutzer ein Foto aufnehmen möchten, müssen sie in der Regel eine Vorschau des Motivs sehen, bevor sie auf den Auslöser klicken. Dazu können Sie mit SurfaceView eine Vorschau dessen zeichnen, was der Kamerasensor erfasst.

Vorschauklasse

Damit Sie eine Vorschau anzeigen können, benötigen Sie eine Vorschauklasse. Für die Vorschau ist eine Implementierung der android.view.SurfaceHolder.Callback-Schnittstelle erforderlich, über die Bilddaten von der Kamerahardware an die Anwendung übergeben werden.

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)
    }
    ...
}
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);
    }
...
}

Die Vorschauklasse muss an das Camera-Objekt übergeben werden, bevor die Livebildvorschau gestartet werden kann, wie im nächsten Abschnitt gezeigt.

Vorschau einrichten und starten

Eine Kamerainstanz und die zugehörige Vorschau müssen in einer bestimmten Reihenfolge erstellt werden, wobei das Kameraobjekt an erster Stelle steht. Im folgenden Snippet wird die Initialisierung der Kamera so verkapselt, dass Camera.startPreview() von der Methode setCamera() aufgerufen wird, wenn der Nutzer etwas tut, um die Kamera zu ändern. Die Vorschau muss auch in der surfaceChanged()-Callback-Methode der Vorschauklasse neu gestartet werden.

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()
    }
}
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();
    }
}

Kameraeinstellungen ändern

Mit den Kameraeinstellungen können Sie die Aufnahmen der Kamera anpassen, z. B. den Zoom oder die Belichtungskorrektur. In diesem Beispiel wird nur die Vorschaugröße geändert. Weitere Beispiele finden Sie im Quellcode der Kameraanwendung.

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()
    }
}
@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();
}

Ausrichtung der Vorschau festlegen

Die meisten Kameraanwendungen sperren das Display im Querformat, da dies die natürliche Ausrichtung des Kamerasensors ist. Diese Einstellung verhindert nicht, dass Sie Fotos im Porträtmodus aufnehmen, da die Ausrichtung des Geräts im EXIF-Header aufgezeichnet wird. Mit der setCameraDisplayOrientation()-Methode können Sie ändern, wie die Vorschau angezeigt wird, ohne dass sich das auf die Aufnahme des Bildes auswirkt. Bei Android-Versionen vor API-Level 14 müssen Sie die Vorschau jedoch beenden, bevor Sie die Ausrichtung ändern, und sie dann neu starten.

Foto aufnehmen

Verwenden Sie die Methode Camera.takePicture(), um ein Foto aufzunehmen, sobald die Vorschau gestartet wurde. Sie können Camera.PictureCallback- und Camera.ShutterCallback-Objekte erstellen und an Camera.takePicture() übergeben.

Wenn Sie Bilder kontinuierlich aufnehmen möchten, können Sie eine Camera.PreviewCallback erstellen, die onPreviewFrame() implementiert. Wenn Sie eine Zwischenlösung benötigen, können Sie nur ausgewählte Vorschauframes erfassen oder eine verzögerte Aktion einrichten, um takePicture() aufzurufen.

Vorabversion neu starten

Nachdem ein Bild aufgenommen wurde, müssen Sie die Vorschau neu starten, bevor der Nutzer ein weiteres Bild aufnehmen kann. In diesem Beispiel wird der Neustart durch Überlastung der Auslösertaste durchgeführt.

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()
}
@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();
}

Vorschau beenden und Kamera freigeben

Wenn Ihre Anwendung die Kamera nicht mehr benötigt, müssen Sie die entsprechenden Ressourcen freigeben. Insbesondere müssen Sie das Camera-Objekt freigeben, da sonst andere Anwendungen abstürzen können, einschließlich neuer Instanzen Ihrer eigenen Anwendung.

Wann sollten Sie die Vorschau beenden und die Kamera loslassen? Wenn die Vorschaufläche zerstört wird, ist es an der Zeit, die Vorschau zu beenden und die Kamera freizugeben, wie in diesen Methoden der Klasse Preview gezeigt.

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
    }
}
@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;
    }
}

Zu Beginn des Tutorials war dieses Verfahren auch Teil der setCamera()-Methode. Das Initialisieren einer Kamera beginnt also immer damit, die Vorschau anzuhalten.