Sterowanie kamerą

W tej lekcji omówimy, jak sterować sprzętem kamery bezpośrednio za pomocą interfejsów API platformy.

Uwaga: ta strona dotyczy wycofanej klasy Camera. Zalecamy korzystanie z CameraX lub, w określonych przypadkach, z Camera2. Zarówno CameraX, jak i Camera2 obsługują Androida 5.0 (poziom interfejsu API 21) i nowsze wersje.

Bezpośrednie sterowanie aparatem urządzenia wymaga znacznie więcej kodu niż wysyłanie próśb o zdjęcia lub filmy z istniejących aplikacji aparatu. Jeśli jednak chcesz utworzyć specjalistyczną aplikację do obsługi aparatu lub coś w pełni zintegrowanego z interfejsem aplikacji, ten artykuł pokaże Ci, jak to zrobić.

Zapoznaj się z tymi powiązanymi materiałami:

Otwieranie obiektu kamery

Uzyskanie instancji obiektu Camera to pierwszy krok w procesie bezpośredniego sterowania kamerą. Podobnie jak w przypadku aplikacji Aparat na Androida zalecanym sposobem uzyskiwania dostępu do aparatu jest otwieranie Camera w osobnym wątku uruchamianym z onCreate(). To dobre rozwiązanie, ponieważ może to trochę potrwać i może spowolnić wątek UI. W bardziej podstawowej implementacji otwarcie aparatu można odłożyć do metody onResume(), aby ułatwić ponowne wykorzystanie kodu i zachować prosty przepływ sterowania.

Wywołanie funkcji Camera.open() powoduje zgłoszenie wyjątku, jeśli aparat jest już używany przez inną aplikację, dlatego umieszczamy je w bloku 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 platforma aparatu obsługuje wiele aparatów. Jeśli używasz starszej wersji interfejsu API i wywołujesz funkcję open() bez argumentu, otrzymasz pierwszą kamerę skierowaną do tyłu.

Tworzenie podglądu z kamery

Zrobienie zdjęcia zwykle wymaga, aby użytkownicy zobaczyli podgląd obiektu przed kliknięciem spustu migawki. Możesz to zrobić, używając SurfaceView, aby narysować podgląd tego, co rejestruje czujnik aparatu.

Klasa podglądu

Aby zacząć wyświetlać podgląd, potrzebujesz klasy podglądu. Podgląd wymaga implementacji interfejsu android.view.SurfaceHolder.Callback, który służy do przekazywania danych obrazu z kamery 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);
    }
...
}

Klasę podglądu należy przekazać do obiektu Camera, zanim będzie można rozpocząć podgląd obrazu na żywo, jak pokazano w następnej sekcji.

Ustawianie i uruchamianie podglądu

Instancję kamery i powiązany z nią podgląd należy utworzyć w określonej kolejności, zaczynając od obiektu kamery. W tym fragmencie kodu proces inicjowania aparatu jest zamknięty w funkcji, tak aby funkcja Camera.startPreview() była wywoływana przez metodę setCamera(), gdy użytkownik wykona działanie, które spowoduje zmianę aparatu. Podgląd należy też ponownie uruchomić w metodzie wywołania zwrotnego klasy podglądu surfaceChanged().

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 robienia zdjęć, od poziomu powiększenia po kompensację ekspozycji. W tym przykładzie zmieniamy tylko rozmiar podglądu. Więcej przykładów znajdziesz w kodzie źródłowym aplikacji Aparat.

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 aparatu blokuje wyświetlacz w trybie poziomym, ponieważ jest to naturalna orientacja czujnika aparatu. To ustawienie nie uniemożliwia robienia zdjęć w trybie portretowym, 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. Jednak w przypadku Androida w wersji starszej niż poziom API 14 musisz zatrzymać podgląd przed zmianą orientacji, a potem go ponownie uruchomić.

Zrób zdjęcie

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

Jeśli chcesz przechwytywać obrazy w sposób ciągły, możesz utworzyć Camera.PreviewCallback, który implementuje onPreviewFrame(). Jeśli chcesz uzyskać coś pośredniego, możesz przechwycić tylko wybrane klatki podglądu lub skonfigurować opóźnione działanie, aby wywołać takePicture().

Ponowne uruchomienie wersji Preview

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

Zatrzymaj podgląd i zwolnij aparat

Gdy aplikacja skończy korzystać z kamery, należy zwolnić miejsce. W szczególności musisz zwolnić obiekt Camera, w przeciwnym razie możesz spowodować awarię innych aplikacji, w tym nowych instancji własnej aplikacji.

Kiedy należy zatrzymać podgląd i zwolnić aparat? Zniszczenie powierzchni podglądu to dość wyraźny sygnał, że należy zatrzymać podgląd i zwolnić kamerę, jak pokazano w tych metodach z 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 tej lekcji ta procedura była też częścią metody setCamera(), więc inicjowanie aparatu zawsze zaczyna się od zatrzymania podglądu.