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.PictureCallback
i Camera.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.