Uwaga: ta strona dotyczy pakietu Camera2. Jeśli aplikacja nie wymaga określonych, niskiego poziomu funkcji z Aparatu2, zalecamy użycie CameraX. Zarówno aplikacja CameraX, jak i Aparat 2 obsługują Androida 5.0 (poziom interfejsu API 21) i nowsze wersje.
Na urządzeniach z Androidem kamery i podglądy z kamery nie zawsze są wyświetlane w tej samej orientacji.
Kamera jest umieszczona w ustalonym położeniu na urządzeniu niezależnie od tego, czy jest to telefon, tablet czy komputer. Gdy orientacja urządzenia się zmienia, zmienia się orientacja aparatu.
W rezultacie aplikacje aparatu zwykle przyjmują stałą zależność między orientacją urządzenia a formatem obrazu podglądu aparatu. Gdy telefon jest w orientacji pionowej, zakładamy, że obraz z aparatu jest wyższy niż szerszy. Po obróceniu telefonu (i aparatu) do orientacji poziomej obraz z aparatu powinien być szerszy niż wysoki.
Te założenia kwestionują jednak nowe formaty, takie jak urządzenia składane i tryby wyświetlania, takie jak wiele okien i wiele wyświetlaczy. Urządzenia składane zmieniają rozmiar wyświetlacza i format obrazu bez zmiany orientacji. Tryb wielu okien ogranicza aplikacje aparatu do części ekranu, skalując podgląd kamery niezależnie od orientacji urządzenia. Tryb wielu wyświetlaczy umożliwia korzystanie z dodatkowych wyświetlaczy, które mogą być w innej orientacji niż wyświetlacz główny.
Orientacja aparatu
Definicja zgodności z Androidem określa, że czujnik obrazu w aparacie musi być ustawiony w taki sposób, aby jego długość była zgodna z długością ekranu. Oznacza to, że gdy urządzenie jest trzymane w orientacji poziomej, aparaty MUSZĄ robić zdjęcia w orientacji poziomej. Dotyczy to zarówno urządzeń w orientacji poziomej, jak i pionowej”.
Układ między aparatem a ekranem maksymalizuje obszar wyświetlania wizjera aparatu w aplikacji aparatu. Czujniki obrazu zazwyczaj przesyłają dane w orientacji poziomej, przy czym najczęściej jest to 4:3.
Naturalna orientacja czujnika aparatu to pozioma. Na ilustracji 1 czujnik przedniego aparatu (aparat skierowany w tym samym kierunku co wyświetlacz) jest obrócony o 270 stopni względem telefonu, aby zachować zgodność z definicją zgodności z Androidem.
Aby udostępnić obrót czujnika aplikacjom, interfejs API camera2 zawiera stałą SENSOR_ORIENTATION
. W przypadku większości telefonów i tabletów urządzenie zgłasza orientację czujnika pod kątem 270 stopni w przypadku aparatów przednich i 90 stopni (perspektywy z tyłu urządzenia) w przypadku tylnych aparatów, co pozwala zbliżyć długą krawędź czujnika do długej krawędzi urządzenia. Kamery w laptopach zgłaszają zwykle
orientację czujnika o wartości 0 lub 180 stopni.
Czujniki obrazu z aparatu przesyłają dane (bufor obrazu) w jego naturalnej orientacji (poziomej), dlatego bufor obrazu musi być obrócony o liczbę stopni określoną przez SENSOR_ORIENTATION
, aby podgląd z aparatu był wyświetlany pionowo w naturalnej orientacji urządzenia. W przypadku aparatów przednich obrót odbywa się w lewo, a w przypadku tylnych aparatów – w prawo.
Na przykład w przypadku przedniego aparatu na ilustracji 1 bufor obrazu generowany przez czujnik w aparacie wygląda tak:
Obraz musi być obrócony o 270 stopni w lewo, tak aby orientacja podglądu była zgodna z orientacją urządzenia:
Tylny aparat utworzy bufor obrazu o takiej samej orientacji jak bufor powyżej, ale SENSOR_ORIENTATION
ma wartość 90 stopni. W efekcie bufor jest obrócony o 90 stopni w prawo.
Obracanie urządzenia
Obrót urządzenia to liczba stopni, o jaką urządzenie zostało obrócone względem naturalnej orientacji. Na przykład telefon w orientacji poziomej może obracać się o 90 lub 270 stopni w zależności od kierunku obrotu.
Aby podgląd kamery był widoczny pionowo, bufor obrazu czujnika aparatu musi być obrócony o tę samą liczbę stopni co obrót urządzenia (a nie tylko w odniesieniu do orientacji czujnika).
Obliczanie orientacji
Prawidłowa orientacja podglądu aparatu uwzględnia orientację czujnika i obrót urządzenia.
Ogólny obrót bufora obrazu czujnika można obliczyć za pomocą tego wzoru:
rotation = (sensorOrientationDegrees - deviceOrientationDegrees * sign + 360) % 360
gdzie sign
to 1
– aparat przedni i -1
– tylny.
W przypadku aparatów przednich bufor obrazu jest obrócony w lewo (zgodnie z naturalną orientacją czujnika). W przypadku tylnych aparatów bufor obrazu czujnika jest obrócony w prawo.
Wyrażenie deviceOrientationDegrees * sign + 360
konwertuje obrót urządzenia w lewo na prawo w przypadku aparatów tylnych (np. konwertuje 270 stopni w lewo na 90 stopni w prawo). Operacja modulo skaluje wynik do mniej niż 360 stopni (np. skalowanie o 540 stopni obrotu do 180).
Różne interfejsy API raportują rotację urządzenia w różny sposób:
Display#getRotation()
umożliwia obrót urządzenia w lewo (z punktu widzenia użytkownika). Ta wartość zostaje w niezmienionej postaci do powyższej formuły.OrientationEventListener#onOrientationChanged()
obraca urządzenie zgodnie z ruchem wskazówek zegara (z perspektywy użytkownika). Ustaw ujemną wartość na potrzeby formuły powyżej.
Przednie aparaty
Oto bufor obrazu wygenerowany przez czujnik aparatu na ilustracji 2:
Aby dostosować orientację czujnika, trzeba obrócić bufor o 270 stopni w lewo (patrz Orientacja aparatu powyżej):
Następnie bufor jest obrócony o dodatkowe 90 stopni w lewo, aby uwzględnić obrócenie urządzenia. Efektem jest poprawna orientacja obrazu podglądu aparatu na ilustracji 2:
Oto kamera obrócona w prawo do orientacji poziomej:
Oto bufor obrazów:
Aby dostosować orientację czujnika, trzeba obrócić bufor o 270 stopni w lewo.
Następnie bufor jest obrócony o kolejne 270 stopni w lewo, aby uwzględnić obrót urządzenia:
Tylne aparaty
Tylne aparaty mają zwykle orientację 90 stopni (patrząc z tyłu urządzenia). Podczas orientowania podglądu z aparatu bufor obrazów z czujnika jest obracany w prawo zgodnie z obrotem czujnika (a nie w lewo jak w przypadku aparatów przednich), a następnie jest obrócony w lewo o wartość obrotu urządzenia.
Oto bufor obrazu z czujnika aparatu na ilustracji 4:
Aby dostosować orientację czujnika, należy obrócić Bufor o 90 stopni w prawo:
Następnie bufor jest obrócony o 270 stopni w lewo, aby uwzględnić obrót urządzenia:
Format obrazu
Format obrazu zmienia się, gdy orientacja urządzenia zmienia się, ale też podczas złożenia i rozłożenia urządzenia, zmiany rozmiaru okna w środowisku z wieloma oknami oraz gdy aplikacje są otwierane na dodatkowych wyświetlaczach.
Orientacja bufora obrazu czujnika aparatu musi być ustalona i skalowana tak, aby pasowała do orientacji i proporcji elementu interfejsu wizjera, ponieważ interfejs dynamicznie zmienia orientację – zarówno ze zmianą orientacji urządzenia, jak i bez niej.
W przypadku nowych formatów, środowisk z wieloma oknami lub z kilkoma wyświetlaczami, jeśli aplikacja zakłada, że podgląd z aparatu ma tę samą orientację co urządzenie (pionowa lub pozioma), może mieć nieprawidłową orientację lub skalę błędną skalę lub mieć obie te wartości.
Na ilustracji 5 aplikacja błędnie uznała, że urządzenie zostało obrócone o 90 stopni w lewo.
Na ilustracji 6 aplikacja nie dostosowała współczynnika proporcji bufora obrazów, aby umożliwić jego prawidłowe skalowanie i dopasowywanie do nowych wymiarów interfejsu podglądu aparatu.
Aplikacje z aparatem o stałej orientacji zwykle mają problemy z urządzeniami składanymi i urządzeniami z dużym ekranem, takimi jak laptopy:
Na ilustracji 7 interfejs aplikacji Aparat jest obrócony, ponieważ jest ona ograniczona tylko do orientacji pionowej. Obraz w wizjerze ma prawidłową orientację względem matrycy.
Ustaw tryb portretowy
Aplikacje aparatu, które nie obsługują trybu wielu okien (resizeableActivity="false"
) i ograniczają orientację (screenOrientation="portrait"
lub screenOrientation="landscape"
), można na urządzeniach z dużym ekranem ustawić w orientacji pionowej, aby prawidłowo orientować się podgląd z aparatu.
Aplikacje w orientacji pionowej (wbudowane) w orientacji pionowej, mimo że format wyświetlacza ma format poziomy. W przypadku aplikacji w orientacji poziomej w orientacji poziomej będą stosowane czarne pasy, mimo że format obrazu jest wyświetlany w orientacji pionowej. Obraz z aparatu jest obrócony w celu dopasowania do interfejsu aplikacji, przycięty tak, by pasował do formatu obrazu podglądu z aparatu, a następnie skalowany w celu wypełnienia podglądu.
Tryb orientacja pionowa jest wywoływany, gdy proporcje obrazu matrycy w kamerze nie są zgodne z formatem obrazu głównego w aplikacji.
Na ilustracji 8 obrócono aplikację aparatu tylko do orientacji pionowej, tak aby interfejs na ekranie laptopa był ustawiony pionowo. Z powodu różnic pomiędzy formatem obrazu w aplikacji w orientacji poziomej a ekranem w orientacji poziomej. Obraz podglądu aparatu został obrócony, by skompensować obrócenie interfejsu aplikacji (ze względu na tryb pionowy). Obraz został przycięty i przeskalowany, by pasował do orientacji pionowej, co zmniejszyło pole widzenia.
Obracanie, przycinanie i skalowanie
Tryb wstawki jest wywoływany w przypadku aplikacji aparatu (tylko w orientacji pionowej) na ekranie o orientacji poziomej:
Aplikacja ma poziome pasy w orientacji pionowej:
Obraz z aparatu jest obrócony o 90 stopni, aby dostosować orientację aplikacji:
Obraz jest przycinany do formatu obrazu podglądu z aparatu, a następnie skalowany tak, by wypełnić podgląd (pole widzenia jest zmniejszone):
Na urządzeniach składanych czujnik aparatu może mieć orientację pionową, a obraz wyświetlacza – w orientacji poziomej:
Podgląd aparatu jest obrócony w celu dopasowania do orientacji czujnika, więc obraz ma prawidłową orientację w wizjerze, a aplikacja tylko w orientacji pionowej jest obrócona.
Aby zapewnić prawidłową orientację podglądu aplikacji i aparatu, w orientacji pionowej trzeba będzie jedynie dodać do niej poziome pasy w orientacji pionowej:
Interfejs API
Od Androida 12 (poziom interfejsu API 31) aplikacje mogą też bezpośrednio kontrolować tryb orientacji pionowej za pomocą właściwości SCALER_ROTATE_AND_CROP
klasy CaptureRequest
.
Wartość domyślna to SCALER_ROTATE_AND_CROP_AUTO
, która umożliwia systemowi wywoływanie trybu portretowego.
SCALER_ROTATE_AND_CROP_90
działa w trybie w orientacji pionowej zgodnie z opisem powyżej.
Nie wszystkie urządzenia obsługują wszystkie wartości parametru SCALER_ROTATE_AND_CROP
. Listę obsługiwanych wartości znajdziesz, odwołanie się do CameraCharacteristics#SCALER_AVAILABLE_ROTATE_AND_CROP_MODES
.
Aparat X
Dzięki bibliotece Jetpack CameraX utworzenie wizjera aparatu dostosowanego do orientacji czujnika i obracania urządzenia to proste zadanie.
Element układu PreviewView
tworzy podgląd kamery, automatycznie dostosowując się do orientacji czujnika, obrotu urządzenia i skalowania. PreviewView
zachowuje współczynnik proporcji obrazu z aparatu, stosując typ skalowania FILL_CENTER
, który wyśrodkowuje obraz, ale może go przyciąć, aby pasował do wymiarów PreviewView
. Aby ustawić obraz z aparatu w formacie letterbox, ustaw typ skali na FIT_CENTER
.
Aby poznać podstawy tworzenia podglądu z aparatu za pomocą PreviewView
, przeczytaj artykuł o implementowaniu podglądu.
Pełną przykładową implementację znajdziesz w repozytorium CameraXBasic
na GitHubie.
Wizjer aparatu
Podobnie jak w przypadku opcji Podgląd, biblioteka Wizjer aparatu zawiera zestaw narzędzi ułatwiających tworzenie podglądu z aparatu. Nie wymaga ona oprogramowania CameraX Core, dzięki czemu można płynnie zintegrować go z istniejącą bazą kodu aparatu Camera2.
Zamiast bezpośrednio używać Surface
, możesz użyć widżetu CameraViewfinder
, aby wyświetlić obraz z kamery dla kamery 2.
CameraViewfinder
używa wewnętrznie TextureView
lub SurfaceView
do wyświetlania obrazu z kamery i stosuje do nich wymagane przekształcenia, aby poprawnie wyświetlać wizjer.
Wiąże się to z skorygowaniem współczynnika proporcji, skali i obrotu.
Aby poprosić o powierzchnię z obiektu CameraViewfinder
, musisz utworzyć ViewfinderSurfaceRequest
.
To żądanie zawiera wymagania dotyczące rozdzielczości powierzchni i informacji o aparacie od CameraCharacteristics
.
Wywołanie requestSurfaceAsync()
powoduje wysłanie żądania do dostawcy platformy, czyli TextureView
lub SurfaceView
, i otrzymuje ListenableFuture
o wartości Surface
.
Wywołanie markSurfaceSafeToRelease()
powiadamia dostawcę platformy, że platforma nie jest potrzebna i mogą zostać zwolnione powiązane zasoby.
Kotlin
fun startCamera(){ val previewResolution = Size(width, height) val viewfinderSurfaceRequest = ViewfinderSurfaceRequest(previewResolution, characteristics) val surfaceListenableFuture = cameraViewfinder.requestSurfaceAsync(viewfinderSurfaceRequest) Futures.addCallback(surfaceListenableFuture, object : FutureCallback<Surface> { override fun onSuccess(surface: Surface) { /* create a CaptureSession using this surface as usual */ } override fun onFailure(t: Throwable) { /* something went wrong */} }, ContextCompat.getMainExecutor(context)) }
Java
void startCamera(){ Size previewResolution = new Size(width, height); ViewfinderSurfaceRequest viewfinderSurfaceRequest = new ViewfinderSurfaceRequest(previewResolution, characteristics); ListenableFuture<Surface> surfaceListenableFuture = cameraViewfinder.requestSurfaceAsync(viewfinderSurfaceRequest); Futures.addCallback(surfaceListenableFuture, new FutureCallback<Surface>() { @Override public void onSuccess(Surface result) { /* create a CaptureSession using this surface as usual */ } @Override public void onFailure(Throwable t) { /* something went wrong */} }, ContextCompat.getMainExecutor(context)); }
Widok powierzchni
SurfaceView
to proste podejście do tworzenia podglądu z aparatu, gdy nie wymaga on przetwarzania i nie jest animowany.
SurfaceView
automatycznie obraca bufor obrazu czujnika aparatu, aby dopasować go do orientacji wyświetlacza. Uwzględnia orientację czujnika i obrót urządzenia. Bufor obrazów jest jednak skalowany, aby pasował do wymiarów SurfaceView
, bez uwzględniania współczynnika proporcji.
Musisz się upewnić, że współczynnik proporcji bufora obrazu odpowiada współczynnikowi proporcji elementu SurfaceView
, co możesz uzyskać, skalując zawartość zasobu SurfaceView
w metodzie onMeasure()
komponentu:
(Kod źródłowy computeRelativeRotation()
znajduje się poniżej w rotacji względnej).
Kotlin
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { val width = MeasureSpec.getSize(widthMeasureSpec) val height = MeasureSpec.getSize(heightMeasureSpec) val relativeRotation = computeRelativeRotation(characteristics, surfaceRotationDegrees) if (previewWidth > 0f && previewHeight > 0f) { /* Scale factor required to scale the preview to its original size on the x-axis. */ val scaleX = if (relativeRotation % 180 == 0) { width.toFloat() / previewWidth } else { width.toFloat() / previewHeight } /* Scale factor required to scale the preview to its original size on the y-axis. */ val scaleY = if (relativeRotation % 180 == 0) { height.toFloat() / previewHeight } else { height.toFloat() / previewWidth } /* Scale factor required to fit the preview to the SurfaceView size. */ val finalScale = min(scaleX, scaleY) setScaleX(1 / scaleX * finalScale) setScaleY(1 / scaleY * finalScale) } setMeasuredDimension(width, height) }
Java
@Override void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = MeasureSpec.getSize(widthMeasureSpec); int height = MeasureSpec.getSize(heightMeasureSpec); int relativeRotation = computeRelativeRotation(characteristics, surfaceRotationDegrees); if (previewWidth > 0f && previewHeight > 0f) { /* Scale factor required to scale the preview to its original size on the x-axis. */ float scaleX = (relativeRotation % 180 == 0) ? (float) width / previewWidth : (float) width / previewHeight; /* Scale factor required to scale the preview to its original size on the y-axis. */ float scaleY = (relativeRotation % 180 == 0) ? (float) height / previewHeight : (float) height / previewWidth; /* Scale factor required to fit the preview to the SurfaceView size. */ float finalScale = Math.min(scaleX, scaleY); setScaleX(1 / scaleX * finalScale); setScaleY(1 / scaleY * finalScale); } setMeasuredDimension(width, height); }
Więcej informacji o implementowaniu SurfaceView
jako podglądu aparatu znajdziesz w sekcji Orientacje aparatu.
Widok tekstury
TextureView
jest mniej wydajny od SurfaceView
, ale wymaga więcej pracy, ale TextureView
daje Ci maksymalną kontrolę nad podglądem z aparatu.
TextureView
obraca bufor obrazu czujnika na podstawie orientacji czujnika, ale nie obsługuje obracania urządzenia ani skalowania podglądu.
Skalowanie i obrót można kodować w przekształceniu macierzy. Więcej informacji o prawidłowym skalowaniu i obracaniu obiektu TextureView
znajdziesz w artykule Obsługa powierzchni, których rozmiar można zmieniać w aplikacji aparatu.
Rotacja względna
Względny obrót czujnika kamery to stopień obrotów wymagany do dopasowania danych wyjściowych czujnika kamery do orientacji urządzenia.
Komponenty takie jak SurfaceView
i TextureView
używają obrotu względnego do określania współczynników skalowania obrazu podglądu na osi x i y. Służy ono również do określania obrotu bufora obrazu czujnika.
Klasy CameraCharacteristics
i Surface
umożliwiają obliczenie względnego obrotu czujnika kamery:
Kotlin
/** * Computes rotation required to transform the camera sensor output orientation to the * device's current orientation in degrees. * * @param characteristics The CameraCharacteristics to query for the sensor orientation. * @param surfaceRotationDegrees The current device orientation as a Surface constant. * @return Relative rotation of the camera sensor output. */ public fun computeRelativeRotation( characteristics: CameraCharacteristics, surfaceRotationDegrees: Int ): Int { val sensorOrientationDegrees = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION)!! // Reverse device orientation for back-facing cameras. val sign = if (characteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT ) 1 else -1 // Calculate desired orientation relative to camera orientation to make // the image upright relative to the device orientation. return (sensorOrientationDegrees - surfaceRotationDegrees * sign + 360) % 360 }
Java
/** * Computes rotation required to transform the camera sensor output orientation to the * device's current orientation in degrees. * * @param characteristics The CameraCharacteristics to query for the sensor orientation. * @param surfaceRotationDegrees The current device orientation as a Surface constant. * @return Relative rotation of the camera sensor output. */ public int computeRelativeRotation( CameraCharacteristics characteristics, int surfaceRotationDegrees ){ Integer sensorOrientationDegrees = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); // Reverse device orientation for back-facing cameras. int sign = characteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT ? 1 : -1; // Calculate desired orientation relative to camera orientation to make // the image upright relative to the device orientation. return (sensorOrientationDegrees - surfaceRotationDegrees * sign + 360) % 360; }
Dane dotyczące okien
Rozmiar ekranu nie powinien być używany do określania wymiarów wizjera w kamerze. Aplikacja aparatu może działać na części ekranu – w trybie wielu okien na urządzeniach mobilnych lub w trybie wolnym w ChromeOS.
WindowManager#getCurrentWindowMetrics()
(dodany do poziomu interfejsu API 30) zwraca rozmiar okna aplikacji, a nie rozmiar ekranu. Metody biblioteki Jetpack WindowManager WindowMetricsCalculator#computeCurrentWindowMetrics()
i WindowInfoTracker#currentWindowMetrics()
zapewniają podobną obsługę wsteczną do interfejsu API na poziomie 14.
Obrót o 180 stopni
Obrót urządzenia o 180 stopni (np. z naturalnej orientacji do naturalnej do góry nogami) nie uruchamia wywołania zwrotnego onConfigurationChanged()
. W związku z tym podgląd z aparatu może być do góry nogami.
Aby wykryć obrót o 180 stopni, wdróż narzędzie DisplayListener
i sprawdź obrót urządzenia, wywołując metodę Display#getRotation()
w wywołaniu zwrotnym onDisplayChanged()
.
Wyjątkowe materiały
Przed Androidem 10 tylko najwyżej widoczna aktywność w środowisku wielu okien miała stan RESUMED
. Było to mylące dla użytkowników, ponieważ
system nie zawierał informacji o tym, które działania zostały wznowione.
W Androidzie 10 (poziom interfejsu API 29) wprowadzono funkcję wielokrotnego wznawiania, gdy wszystkie widoczne aktywności mają stan RESUMED
. Widoczne aktywności mogą nadal przechodzić w stan PAUSED
, jeśli na przykład aktywność jest umieszczona nad nią przezroczystą lub nie można jej zaznaczyć, jak w trybie obraz w obrazie (patrz Obsługa funkcji obraz w obrazie).
Aplikacja korzystająca z kamery, mikrofonu lub jakichkolwiek zasobów specjalnych na poziomie API 29 lub wyższym musi obsługiwać wielokrotne wznawianie. Na przykład, jeśli do 3 wznawianych aktywności chce się używać kamery, dostęp do tego wyjątkowego zasobu będzie mieć tylko jedna z nich. Każde działanie musi zawierać wywołanie zwrotne onDisconnected()
, które informuje o wywołaniu zapobiegawczym dostępu do kamery przez działanie o wyższym priorytecie.
Więcej informacji znajdziesz w artykule Wielokrotne wznawianie.
Dodatkowe materiały
- Przykładowy plik z aparatem Camera2 znajdziesz w aplikacji Camera2Policy na GitHubie.
- Informacje o przypadku użycia podglądu aparatu CameraX znajdziesz w artykule o implementacji podglądu.
- Przykład implementacji podglądu aparatu CameraX znajdziesz w repozytorium CameraX Basic na GitHubie.
- Więcej informacji o podglądzie aparatu w ChromeOS znajdziesz w sekcji Orientacje aparatu.
- Informacje na temat tworzenia aplikacji na urządzenia składane znajdziesz w artykule Więcej informacji o urządzeniach składanych.