Sterowanie kamerą

W tej lekcji omawiamy sterowanie sprzętem kamery bezpośrednio za pomocą interfejsów API frameworku.

Uwaga: ta strona dotyczy klasy Camera, która została wycofana. Zalecamy korzystanie z CameraX lub, w przypadku określonych zastosowań, z Camera2. Zarówno CameraX, jak i Camera2 obsługują Androida 5.0 (poziom interfejsu API 21) i nowsze.

Bezpośrednie sterowanie aparatem urządzenia wymaga znacznie więcej kodu niż żądanie zdjęć lub filmów z dotychczasowych aplikacji do obsługi aparatu. Jeśli jednak chcesz utworzyć specjalistyczną aplikację do obsługi kamery lub coś, co jest w pełni zintegrowane z interfejsem aplikacji, ta lekcja pokaże Ci, jak to zrobić.

Zapoznaj się z tymi materiałami:

Otwieranie obiektu aparatu

Pobieranie instancji obiektu Camera jest pierwszym krokiem w procesie bezpośredniego sterowania kamerą. Podobnie jak w przypadku aplikacji Aparat na Androidzie, zalecany sposób uzyskania dostępu do aparatu to otwarcie Camera na osobnym wątku, który jest uruchamiany z poziomu onCreate(). Takie podejście jest dobrym pomysłem, ponieważ może zająć trochę czasu i może spowolnić wątek interfejsu. W prostszym wdrożeniu otwarcie aparatu można odłożyć do metody onResume(), aby ułatwić ponowne użycie kodu i utrzymać prostotę przepływu sterowania.

Wywołanie funkcji Camera.open() powoduje wyjątek, jeśli aparat jest już używany przez inną aplikację, więc otaczamy go blokiem 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;
    }
}

Od poziomu API 9 framework aparatu obsługuje wiele aparatów. Jeśli używasz starszego interfejsu API i wywołujesz open() bez argumentu, otrzymasz pierwszą tylną kamerę.

Tworzenie podglądu z aparatu

Zdjęcie wymaga zwykle, aby użytkownicy zobaczyli podgląd obiektu przed naciśnięciem migawki. W tym celu możesz użyć SurfaceView, aby wyświetlić podgląd tego, co rejestruje czujnik aparatu.

Klasa podglądu

Aby wyświetlić podgląd, musisz mieć zajęcia z podglądem. Podgląd wymaga implementacji interfejsu android.view.SurfaceHolder.Callback, który służy do przekazywania danych obrazu z aparatu do aplikacji.

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

Klasa podglądu musi zostać przekazana do obiektu Camera, zanim można rozpocząć podgląd obrazu na żywo, jak pokazano w następnej sekcji.

Konfigurowanie i uruchamianie podglądu

Przykład kamery i powiązany z nim podgląd muszą zostać utworzone w określonej kolejności, przy czym obiekt kamery musi być utworzony jako pierwszy. W poniżej zamieszczonym fragmencie kodu proces inicjowania aparatu jest opakowany, tak aby metoda Camera.startPreview() wywoływała metodę setCamera(), gdy tylko użytkownik wprowadzi zmiany w aparacie. Podgląd musi też zostać ponownie uruchomiony w metodzie wywołania surfaceChanged() klasy 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();
    }
}

Modyfikowanie ustawień aparatu

Ustawienia aparatu zmieniają sposób, w jaki aparat robi zdjęcia, od poziomu zoomu do kompensacji ekspozycji. W tym przykładzie zmienia się tylko rozmiar podglądu. W źródle aplikacji Aparat znajdziesz więcej przykładów.

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

Ustawianie orientacji podglądu

Większość aplikacji do obsługi aparatu blokuje wyświetlacz w orientacji poziomej, ponieważ jest to naturalna orientacja czujnika aparatu. To ustawienie nie uniemożliwia robienia zdjęć w układzie pionowym, ponieważ orientacja urządzenia jest zapisywana w nagłówku EXIF. Metoda setCameraDisplayOrientation() pozwala zmienić sposób wyświetlania podglądu bez wpływu na sposób rejestrowania obrazu. Na urządzeniach z Androidem w wersji starszej niż poziom interfejsu API 14 przed zmianą orientacji musisz jednak zatrzymać podgląd, a potem go ponownie uruchomić.

Zrób zdjęcie

Po rozpoczęciu podglądu użyj metody Camera.takePicture(), aby zrobić zdjęcie. Możesz tworzyć obiekty Camera.PictureCallbackCamera.ShutterCallback oraz przekazywać je do funkcji Camera.takePicture().

Jeśli chcesz ciągle pobierać obrazy, możesz utworzyć Camera.PreviewCallback, który implementuje onPreviewFrame(). Jeśli chcesz uzyskać coś pośredniego, możesz rejestrować tylko wybrane klatki podglądu lub skonfigurować opóźnione wywołanie takePicture().

Ponownie uruchom podgląd

Po zrobieniu zdjęcia musisz ponownie uruchomić podgląd, zanim użytkownik będzie mógł zrobić kolejne zdjęcie. W tym przykładzie ponowne uruchomienie jest wykonywane przez przeciążenie przycisku migawki.

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

Zatrzymanie podglądu i zwolnienie kamery

Gdy aplikacja skończy korzystać z kamery, nadszedł czas na uporządkowanie. W szczególności musisz zwolnić obiekt Camera, w przeciwnym razie ryzykujesz zawieszenie innych aplikacji, w tym nowych instancji własnej aplikacji.

Kiedy należy zatrzymać podgląd i zwolnić kamerę? No cóż, zniszczenie powierzchni podglądu to dobry znak, że czas zatrzymać podgląd i zwolnić kamerę, tak jak w metodach klasy 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;
    }
}

Wcześniej w lekcji ta procedura była również częścią metody setCamera(), więc inicjowanie aparatu zawsze zaczyna się od zatrzymania podglądu.