Sterowanie kamerą

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

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

Aby dowiedzieć się, jak przejść z Camera1 na CameraX, przeczytaj artykuł Migracja z Camera1 na CameraX. Możesz też użyć umiejętności agentowej do migracji z Camera1 do CameraX.

Bezpośrednie sterowanie aparatem urządzenia wymaga znacznie więcej kodu niż żądanie zdjęć lub filmów 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, z tej lekcji dowiesz się, jak to zrobić.

Zapoznaj się z tymi przydatnymi materiałami:

Otwórz obiekt kamery

Pobranie instancji obiektu Camera to pierwszy krok w procesie bezpośredniego sterowania aparatem. Podobnie jak w przypadku aplikacji Aparat na Androida, zalecanym sposobem uzyskania dostępu do aparatu jest otwarcie Camera w osobnym wątku uruchamianym z onCreate(). To podejście jest dobrym pomysłem, ponieważ może to trochę potrwać i spowolnić wątek UI. W prostszej implementacji otwarcie aparatu można odroczyć do metody onResume(), aby ułatwić ponowne użycie kodu i zachować prosty przepływ sterowania.

Wywołanie Camera.open() powoduje wyjątek, 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 interfejsu API 9 platforma aparatu obsługuje wiele aparatów. Jeśli używasz starszej wersji interfejsu API i wywołujesz open() bez argumentu, otrzymasz pierwszą tylną kamerę.

Tworzenie podglądu z aparatu

Zrobienie zdjęcia zwykle wymaga, aby użytkownicy przed kliknięciem migawki zobaczyli podgląd obiektu. W tym celu możesz użyć elementu SurfaceView, aby wyświetlać podgląd tego, co rejestruje czujnik aparatu.

Klasa podglądu

Aby wyświetlić podgląd, musisz mieć klasę podglądu. Podgląd wymaga implementacji interfejsu android.view.SurfaceHolder.Callback, który służy do przekazywania danych obrazu ze sprzętu 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);
    }
...
}

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, przy czym obiekt kamery musi być pierwszy. W poniższym fragmencie kodu proces inicjowania kamery jest hermetyzowany, dzięki czemu metoda Camera.startPreview() jest wywoływana przez metodę setCamera(), gdy użytkownik wykona działanie, które zmienia kamerę. Podgląd musi też zostać ponownie uruchomiony 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 zmieniany jest 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 Androidzie przed poziomem interfejsu API 14 musisz zatrzymać podgląd przed zmianą orientacji, a następnie uruchomić go ponownie.

Zrób zdjęcie

Użyj metody Camera.takePicture(), aby zrobić zdjęcie po uruchomieniu podglądu. Możesz utworzyć obiekty Camera.PictureCallback i Camera.ShutterCallback i przekazać je do Camera.takePicture().

Jeśli chcesz stale pobierać obrazy, możesz utworzyć Camera.PreviewCallback, który implementuje onPreviewFrame(). Jeśli chcesz wybrać coś pomiędzy, możesz przechwytywać tylko wybrane klatki podglądu lub skonfigurować opóźnione działanie, aby wywołać metodę takePicture().

Ponowne uruchamianie podglądu

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 po przeciążeniu 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();
}

Zatrzymywanie podglądu i zwalnianie aparatu

Gdy aplikacja skończy korzystać z aparatu, należy zwolnić zasoby. W szczególności musisz zwolnić obiekt Camera, aby nie spowodować awarii innych aplikacji, w tym nowych instancji własnej aplikacji.

Kiedy należy zatrzymać podgląd i zwolnić aparat? Zniszczenie powierzchni podglądu to dobry moment na zatrzymanie podglądu i zwolnienie aparatu, 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.