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.
Aplikacja aparatu może używać więcej niż jednego strumienia klatek jednocześnie. W niektórych przypadkach różne strumienie wymagają nawet innej rozdzielczości klatki lub formatu pikseli. Oto niektóre typowe przypadki użycia:
- Nagrywanie wideo: jeden strumień do podglądu, a drugi jest kodowany i zapisywany w pliku.
- Skanowanie kodów kreskowych: jeden strumień do podglądu, a drugi do wykrywania kodów kreskowych.
- Fotografia obliczeniowa: jeden strumień do podglądu, a drugi do wykrywania twarzy i scen.
Podczas przetwarzania ramek wiąże się z nim nieistotny koszt wydajności, który jest mnożony podczas przetwarzania równoległego strumienia lub potoku.
Zasoby takie jak procesory, GPU i DSP mogą korzystać z możliwości ponownego przetwarzania platformy, ale zasoby takie jak pamięć będą rosnąć liniowo.
Wiele celów na żądanie
Kilka strumieni kamer można połączyć w jeden CameraCaptureRequest
.
Ten fragment kodu pokazuje, jak skonfigurować sesję z kamerą – jeden strumień do podglądu z aparatu, a drugi do przetwarzania obrazu:
Kotlin
val session: CameraCaptureSession = ... // from CameraCaptureSession.StateCallback // You will use the preview capture template for the combined streams // because it is optimized for low latency; for high-quality images, use // TEMPLATE_STILL_CAPTURE, and for a steady frame rate use TEMPLATE_RECORD val requestTemplate = CameraDevice.TEMPLATE_PREVIEW val combinedRequest = session.device.createCaptureRequest(requestTemplate) // Link the Surface targets with the combined request combinedRequest.addTarget(previewSurface) combinedRequest.addTarget(imReaderSurface) // In this simple case, the SurfaceView gets updated automatically. ImageReader // has its own callback that you have to listen to in order to retrieve the // frames so there is no need to set up a callback for the capture request session.setRepeatingRequest(combinedRequest.build(), null, null)
Java
CameraCaptureSession session = …; // from CameraCaptureSession.StateCallback // You will use the preview capture template for the combined streams // because it is optimized for low latency; for high-quality images, use // TEMPLATE_STILL_CAPTURE, and for a steady frame rate use TEMPLATE_RECORD CaptureRequest.Builder combinedRequest = session.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); // Link the Surface targets with the combined request combinedRequest.addTarget(previewSurface); combinedRequest.addTarget(imReaderSurface); // In this simple case, the SurfaceView gets updated automatically. ImageReader // has its own callback that you have to listen to in order to retrieve the // frames so there is no need to set up a callback for the capture request session.setRepeatingRequest(combinedRequest.build(), null, null);
Jeśli prawidłowo skonfigurujesz platformy docelowe, ten kod wygeneruje tylko strumienie spełniające minimalną liczbę klatek na sekundę określone przez StreamComfigurationMap.GetOutputMinFrameDuration(int, Size)
i StreamComfigurationMap.GetOutputStallDuration(int, Size)
.
Rzeczywista wydajność różni się w zależności od urządzenia, ale Android gwarantuje obsługę określonych kombinacji w zależności od 3 zmiennych: typu wyjściowego, rozmiaru wyjściowego i poziomu sprzętu.
Użycie nieobsługiwanej kombinacji zmiennych może działać z małą liczbą klatek, a w przeciwnym razie uruchamia jedno z nieudanych wywołań zwrotnych.
W dokumentacji createCaptureSession
opisano, co będzie działać prawidłowo.
Typ wyjściowy
Typ danych wyjściowych odnosi się do formatu kodowania ramek. Możliwe wartości to PRIV, YUV, JPEG i RAW. Opisuje je w dokumentacji createCaptureSession
.
Jeśli wybierasz typ danych wyjściowych aplikacji, to jeśli chcesz zmaksymalizować zgodność, stosuj metodę ImageFormat.YUV_420_888
do analizy klatek, a ImageFormat.JPEG
– do obrazów nieruchomych. W scenariuszach wyświetlania podglądu i nagrywania prawdopodobnie będziesz używać SurfaceView
, TextureView
, MediaRecorder
, MediaCodec
lub RenderScript.Allocation
. W takich przypadkach nie określaj formatu obrazu. W celu zapewnienia zgodności będzie on liczony jako ImageFormat.PRIVATE
niezależnie od rzeczywistego formatu używanego wewnętrznie. Aby wysłać zapytanie o formaty obsługiwane przez urządzenie z uwzględnieniem jego CameraCharacteristics
, użyj tego kodu:
Kotlin
val characteristics: CameraCharacteristics = ... val supportedFormats = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP).outputFormats
Java
CameraCharacteristics characteristics = …; int[] supportedFormats = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP).getOutputFormats();
Rozmiar wyjściowy
Wszystkie dostępne rozmiary wyjściowe są wymienione według StreamConfigurationMap.getOutputSizes()
, ale tylko dwa są związane ze zgodnością: PREVIEW
i MAXIMUM
. Rozmiary stanowią górną granicę. Jeśli rozmiar PREVIEW
będzie działać, będzie działać też wszystko o rozmiarze mniejszym niż PREVIEW
. To samo dotyczy witryny MAXIMUM
. Opis tych rozmiarów znajdziesz w dokumentacji CameraDevice
.
Dostępne rozmiary wyjściowe zależą od wybranego formatu. Biorąc pod uwagę CameraCharacteristics
i format, możesz wysłać zapytanie o dostępne rozmiary wyjściowe w ten sposób:
Kotlin
val characteristics: CameraCharacteristics = ... val outputFormat: Int = ... // such as ImageFormat.JPEG val sizes = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) .getOutputSizes(outputFormat)
Java
CameraCharacteristics characteristics = …; int outputFormat = …; // such as ImageFormat.JPEG Size[] sizes = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) .getOutputSizes(outputFormat);
W przypadku podglądu aparatu i nagrywania użyj klasy docelowej, aby określić obsługiwane rozmiary. Format jest obsługiwany przez samą platformę aparatu:
Kotlin
val characteristics: CameraCharacteristics = ... val targetClass: Class <T> = ... // such as SurfaceView::class.java val sizes = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) .getOutputSizes(targetClass)
Java
CameraCharacteristics characteristics = …; int outputFormat = …; // such as ImageFormat.JPEG Size[] sizes = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) .getOutputSizes(outputFormat);
Aby uzyskać rozmiar MAXIMUM
, posortuj rozmiary wyjściowe według obszaru i zwróć największą wartość:
Kotlin
fun <T>getMaximumOutputSize( characteristics: CameraCharacteristics, targetClass: Class <T>, format: Int? = null): Size { val config = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) // If image format is provided, use it to determine supported sizes; or else use target class val allSizes = if (format == null) config.getOutputSizes(targetClass) else config.getOutputSizes(format) return allSizes.maxBy { it.height * it.width } }
Java
@RequiresApi(api = Build.VERSION_CODES.N) <T> Size getMaximumOutputSize(CameraCharacteristics characteristics, Class <T> targetClass, Integer format) { StreamConfigurationMap config = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); // If image format is provided, use it to determine supported sizes; else use target class Size[] allSizes; if (format == null) { allSizes = config.getOutputSizes(targetClass); } else { allSizes = config.getOutputSizes(format); } return Arrays.stream(allSizes).max(Comparator.comparing(s -> s.getHeight() * s.getWidth())).get(); }
PREVIEW
odnosi się do rozmiaru najlepiej dopasowanego do rozdzielczości ekranu urządzenia lub do 1080p (1920 x 1080), która jest mniejsza. Współczynnik proporcji może nie odpowiadać dokładnie proporcjom ekranu. Aby wyświetlić obraz w trybie pełnoekranowym, konieczne może być dodanie czarnych pasów lub przycięcie do transmisji. Aby uzyskać właściwy rozmiar podglądu, porównaj dostępne rozmiary wyjściowe z rozmiarem wyświetlacza, uwzględniając przy tym, że wyświetlacz może być obrócony.
Poniższy kod określa klasę pomocniczą SmartSize
, co ułatwia porównywanie rozmiarów:
Kotlin
/** Helper class used to pre-compute shortest and longest sides of a [Size] */ class SmartSize(width: Int, height: Int) { var size = Size(width, height) var long = max(size.width, size.height) var short = min(size.width, size.height) override fun toString() = "SmartSize(${long}x${short})" } /** Standard High Definition size for pictures and video */ val SIZE_1080P: SmartSize = SmartSize(1920, 1080) /** Returns a [SmartSize] object for the given [Display] */ fun getDisplaySmartSize(display: Display): SmartSize { val outPoint = Point() display.getRealSize(outPoint) return SmartSize(outPoint.x, outPoint.y) } /** * Returns the largest available PREVIEW size. For more information, see: * https://d.android.com/reference/android/hardware/camera2/CameraDevice */ fun <T>getPreviewOutputSize( display: Display, characteristics: CameraCharacteristics, targetClass: Class <T>, format: Int? = null ): Size { // Find which is smaller: screen or 1080p val screenSize = getDisplaySmartSize(display) val hdScreen = screenSize.long >= SIZE_1080P.long || screenSize.short >= SIZE_1080P.short val maxSize = if (hdScreen) SIZE_1080P else screenSize // If image format is provided, use it to determine supported sizes; else use target class val config = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)!! if (format == null) assert(StreamConfigurationMap.isOutputSupportedFor(targetClass)) else assert(config.isOutputSupportedFor(format)) val allSizes = if (format == null) config.getOutputSizes(targetClass) else config.getOutputSizes(format) // Get available sizes and sort them by area from largest to smallest val validSizes = allSizes .sortedWith(compareBy { it.height * it.width }) .map { SmartSize(it.width, it.height) }.reversed() // Then, get the largest output size that is smaller or equal than our max size return validSizes.first { it.long <= maxSize.long && it.short <= maxSize.short }.size }
Java
/** Helper class used to pre-compute shortest and longest sides of a [Size] */ class SmartSize { Size size; double longSize; double shortSize; public SmartSize(Integer width, Integer height) { size = new Size(width, height); longSize = max(size.getWidth(), size.getHeight()); shortSize = min(size.getWidth(), size.getHeight()); } @Override public String toString() { return String.format("SmartSize(%sx%s)", longSize, shortSize); } } /** Standard High Definition size for pictures and video */ SmartSize SIZE_1080P = new SmartSize(1920, 1080); /** Returns a [SmartSize] object for the given [Display] */ SmartSize getDisplaySmartSize(Display display) { Point outPoint = new Point(); display.getRealSize(outPoint); return new SmartSize(outPoint.x, outPoint.y); } /** * Returns the largest available PREVIEW size. For more information, see: * https://d.android.com/reference/android/hardware/camera2/CameraDevice */ @RequiresApi(api = Build.VERSION_CODES.N) <T> Size getPreviewOutputSize( Display display, CameraCharacteristics characteristics, Class <T> targetClass, Integer format ){ // Find which is smaller: screen or 1080p SmartSize screenSize = getDisplaySmartSize(display); boolean hdScreen = screenSize.longSize >= SIZE_1080P.longSize || screenSize.shortSize >= SIZE_1080P.shortSize; SmartSize maxSize; if (hdScreen) { maxSize = SIZE_1080P; } else { maxSize = screenSize; } // If image format is provided, use it to determine supported sizes; else use target class StreamConfigurationMap config = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); if (format == null) assert(StreamConfigurationMap.isOutputSupportedFor(targetClass)); else assert(config.isOutputSupportedFor(format)); Size[] allSizes; if (format == null) { allSizes = config.getOutputSizes(targetClass); } else { allSizes = config.getOutputSizes(format); } // Get available sizes and sort them by area from largest to smallest List <Size> sortedSizes = Arrays.asList(allSizes); List <SmartSize> validSizes = sortedSizes.stream() .sorted(Comparator.comparing(s -> s.getHeight() * s.getWidth())) .map(s -> new SmartSize(s.getWidth(), s.getHeight())) .sorted(Collections.reverseOrder()).collect(Collectors.toList()); // Then, get the largest output size that is smaller or equal than our max size return validSizes.stream() .filter(s -> s.longSize <= maxSize.longSize && s.shortSize <= maxSize.shortSize) .findFirst().get().size; }
Sprawdzanie obsługiwanego poziomu sprzętu
Aby określić dostępne możliwości w czasie działania, sprawdź obsługiwany poziom sprzętu za pomocą CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL
.
Za pomocą obiektu CameraCharacteristics
możesz pobrać poziom sprzętu za pomocą jednej instrukcji:
Kotlin
val characteristics: CameraCharacteristics = ... // Hardware level will be one of: // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY, // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL, // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED, // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL, // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3 val hardwareLevel = characteristics.get( CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL)
Java
CameraCharacteristics characteristics = ...; // Hardware level will be one of: // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY, // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL, // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED, // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL, // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3 Integer hardwareLevel = characteristics.get( CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
Łączenie wszystkich elementów w całość
Na podstawie typu danych wyjściowych, rozmiaru danych wyjściowych i poziomu sprzętu możesz określić, które kombinacje strumieni są prawidłowe. Poniższy wykres przedstawia podsumowanie konfiguracji obsługiwanych przez CameraDevice
na poziomie sprzętu LEGACY
.
Cel 1 | Cel 2 | Cel 3 | Przykładowe przypadki użycia | |||
---|---|---|---|---|---|---|
Typ | Maksymalny rozmiar | Typ | Maksymalny rozmiar | Typ | Maksymalny rozmiar | |
PRIV |
MAXIMUM |
Prosty podgląd, przetwarzanie wideo za pomocą GPU lub nagrywanie wideo bez podglądu. | ||||
JPEG |
MAXIMUM |
Zdjęcie nieruchome bez wizjera. | ||||
YUV |
MAXIMUM |
Przetwarzanie filmu lub obrazu w aplikacji. | ||||
PRIV |
PREVIEW |
JPEG |
MAXIMUM |
Standardowy obraz. | ||
YUV |
PREVIEW |
JPEG |
MAXIMUM |
Przetwarzanie w aplikacji i nadal robienie zdjęć. | ||
PRIV |
PREVIEW |
PRIV |
PREVIEW |
Nagranie standardowe. | ||
PRIV |
PREVIEW |
YUV |
PREVIEW |
Podgląd plus przetwarzanie w aplikacji. | ||
PRIV |
PREVIEW |
YUV |
PREVIEW |
Podgląd plus przetwarzanie w aplikacji. | ||
PRIV |
PREVIEW |
YUV |
PREVIEW |
JPEG |
MAXIMUM |
Nadal trwa rejestrowanie i przetwarzanie w aplikacji. |
LEGACY
to najniższy możliwy poziom sprzętowy. Z tej tabeli wynika, że każde urządzenie, które obsługuje Camera2 (poziom interfejsu API 21 lub wyższy) może przy użyciu odpowiedniej konfiguracji wygenerować maksymalnie 3 jednoczesne strumienie, a jeśli wydajność nie jest zbyt duża, aby wydajność wynikała z ograniczeń dotyczących pamięci, procesora lub temperatury urządzenia.
Aplikacja musi też skonfigurować bufory danych wyjściowych kierowania. Aby na przykład kierować reklamy na urządzenie z poziomem sprzętu LEGACY
, możesz skonfigurować 2 docelowe przestrzenie wyjściowe – jedną za pomocą ImageFormat.PRIVATE
i drugą z użyciem ImageFormat.YUV_420_888
. Jest to obsługiwane połączenie podczas korzystania z rozmiaru PREVIEW
. Uzyskanie wymaganych rozmiarów podglądu dla identyfikatora aparatu za pomocą funkcji zdefiniowanej wcześniej w tym temacie wymaga następującego kodu:
Kotlin
val characteristics: CameraCharacteristics = ... val context = this as Context // assuming you are inside of an activity val surfaceViewSize = getPreviewOutputSize( context, characteristics, SurfaceView::class.java) val imageReaderSize = getPreviewOutputSize( context, characteristics, ImageReader::class.java, format = ImageFormat.YUV_420_888)
Java
CameraCharacteristics characteristics = ...; Context context = this; // assuming you are inside of an activity Size surfaceViewSize = getPreviewOutputSize( context, characteristics, SurfaceView.class); Size imageReaderSize = getPreviewOutputSize( context, characteristics, ImageReader.class, format = ImageFormat.YUV_420_888);
Wymaga oczekiwania, aż SurfaceView
użyje podanych wywołań zwrotnych:
Kotlin
val surfaceView = findViewById <SurfaceView>(...) surfaceView.holder.addCallback(object : SurfaceHolder.Callback { override fun surfaceCreated(holder: SurfaceHolder) { // You do not need to specify image format, and it will be considered of type PRIV // Surface is now ready and you could use it as an output target for CameraSession } ... })
Java
SurfaceView surfaceView = findViewById <SurfaceView>(...); surfaceView.getHolder().addCallback(new SurfaceHolder.Callback() { @Override public void surfaceCreated(@NonNull SurfaceHolder surfaceHolder) { // You do not need to specify image format, and it will be considered of type PRIV // Surface is now ready and you could use it as an output target for CameraSession } ... });
Możesz wymusić dopasowanie parametru SurfaceView
do rozmiaru wyjściowego kamery, wywołując metodę SurfaceHolder.setFixedSize()
. Możesz też zastosować metodę podobną do AutoFitSurfaceView
z wspólnego modułu
przykładów z kamer na GitHubie, który ustawia rozmiar bezwzględny, biorąc pod uwagę zarówno format obrazu, jak i dostępną przestrzeń, oraz automatyczne dostosowywanie po pojawieniu się zmian aktywności.
Skonfigurowanie drugiej platformy z poziomu ImageReader
w odpowiednim formacie jest łatwiejsze, ponieważ nie ma żadnych wywołań zwrotnych do oczekiwania:
Kotlin
val frameBufferCount = 3 // just an example, depends on your usage of ImageReader val imageReader = ImageReader.newInstance( imageReaderSize.width, imageReaderSize.height, ImageFormat.YUV_420_888, frameBufferCount)
Java
int frameBufferCount = 3; // just an example, depends on your usage of ImageReader ImageReader imageReader = ImageReader.newInstance( imageReaderSize.width, imageReaderSize.height, ImageFormat.YUV_420_888, frameBufferCount);
Jeśli używasz blokującego bufora docelowego, takiego jak ImageReader
, odrzuć ramki po ich użyciu:
Kotlin
imageReader.setOnImageAvailableListener({ val frame = it.acquireNextImage() // Do something with "frame" here it.close() }, null)
Java
imageReader.setOnImageAvailableListener(listener -> { Image frame = listener.acquireNextImage(); // Do something with "frame" here listener.close(); }, null);
LEGACY
– na poziomie sprzętu kierowane są urządzenia o najniższym wspólnym mianowniku. Możesz dodać rozgałęzienie warunkowe i użyć rozmiaru RECORD
dla jednej z przestrzeni docelowych na urządzeniach z poziomem sprzętu LIMITED
, a nawet zwiększyć ten rozmiar do MAXIMUM
w przypadku urządzeń z poziomem sprzętu FULL
.