Uwaga: ta strona dotyczy pakietu Camera2. Jeśli Twoja aplikacja nie wymaga określonych funkcji niskiego poziomu z Camera2, zalecamy używanie CameraX. Zarówno CameraX, jak i Camera2 obsługują Androida 5.0 (poziom interfejsu API 21) i nowsze wersje.
Aplikacja aparatu może używać więcej niż 1 strumienia klatek jednocześnie. W niektórych przypadkach różne strumienie wymagają nawet innej rozdzielczości klatek lub formatu pikseli. Oto kilka typowych przypadków użycia:
- Nagrywanie filmu: jeden strumień do podglądu, a drugi jest kodowany i zapisywany w pliku.
- Skanowanie kodów kreskowych: jeden strumień do podglądu, drugi do wykrywania kodów kreskowych.
- Fotografia cyfrowa: jeden strumień do podglądu, drugi do wykrywania twarzy lub sceny.
Przetwarzanie klatek wiąże się z niemałym kosztem wydajności, który jest mnożony podczas przetwarzania równoległego strumienia lub potoku.
Zasoby takie jak CPU, GPU i DSP mogą korzystać z możliwości ponownego przetwarzania, ale zasoby takie jak pamięć rosną liniowo.
Wiele celów na żądanie
Kilka strumieni z kamery można połączyć w jedną CameraCaptureRequest. Poniższy fragment kodu pokazuje, jak skonfigurować sesję kamery z jednym strumieniem do podglądu z kamery i drugim 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 basic 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 basic 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 powierzchnie docelowe, ten kod będzie generować tylko strumienie, które spełniają minimalną liczbę klatek na sekundę określoną 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 danych wyjściowych, rozmiaru danych wyjściowych i poziomu sprzętu.
Użycie nieobsługiwanej kombinacji zmiennych może działać przy niskiej liczbie klatek. Jeśli tak się nie stanie, zostanie wywołane jedno z wywołań zwrotnych błędu. Dokumentacja createCaptureSession opisuje, co jest gwarantowane.
Typ wyjściowy
Typ danych wyjściowych odnosi się do formatu, w którym kodowane są klatki. Możliwe wartości to PRIV, YUV, JPEG i RAW. Są one opisane w dokumentacji createCaptureSession.
Jeśli chcesz zmaksymalizować zgodność, podczas wybierania typu danych wyjściowych aplikacji użyj ImageFormat.YUV_420_888 do analizy klatek i ImageFormat.JPEG do zdjęć. W przypadku podglądu i nagrywania prawdopodobnie będziesz używać elementu SurfaceView, TextureView, MediaRecorder, MediaCodec lub RenderScript.Allocation. W takich przypadkach nie podawaj formatu pliku graficznego. Ze względu na zgodność będzie on traktowany jako ImageFormat.PRIVATE niezależnie od rzeczywistego formatu używanego wewnętrznie. Aby sprawdzić formaty obsługiwane przez urządzenie o danym identyfikatorze 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 w StreamConfigurationMap.getOutputSizes(), ale tylko 2 z nich są związane ze zgodnością: PREVIEW i MAXIMUM. Rozmiary są górnymi granicami. Jeśli coś o rozmiarze PREVIEW działa, to wszystko o rozmiarze mniejszym niż PREVIEW też będzie działać. To samo dotyczy MAXIMUM. Te rozmiary są opisane w dokumentacji CameraDevice.
Dostępne rozmiary wyjściowe zależą od wybranego formatu. Mając 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 i nagrywania z kamery użyj klasy docelowej, aby określić obsługiwane rozmiary. Format będzie 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ększy:
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 × 1080) – w zależności od tego, która z tych wartości jest mniejsza. Format obrazu może nie odpowiadać dokładnie formatowi ekranu, więc aby wyświetlić transmisję w trybie pełnoekranowym, może być konieczne zastosowanie formatu letterbox lub przycięcie obrazu. Aby uzyskać odpowiedni rozmiar podglądu, porównaj dostępne rozmiary wyjściowe z rozmiarem wyświetlacza, biorąc pod uwagę, że wyświetlacz może być obrócony.
Poniższy kod definiuje klasę pomocniczą SmartSize, która ułatwi 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 funkcje 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
Typ wyjściowy, rozmiar wyjściowy i poziom sprzętu pozwalają określić, które kombinacje strumieni są prawidłowe. Poniższa tabela przedstawia konfiguracje obsługiwane przez CameraDevice na poziomie sprzętowym LEGACY.
| Cel 1 | Cel 2 | Cel 3 | Przykładowe przypadki użycia | |||
|---|---|---|---|---|---|---|
| Typ | Wielkość maksymalna | Typ | Wielkość maksymalna | Typ | Wielkość maksymalna | |
PRIV |
MAXIMUM |
Prosty podgląd, przetwarzanie wideo na GPU lub nagrywanie wideo bez podglądu. | ||||
JPEG |
MAXIMUM |
Robienie zdjęć bez wizjera. | ||||
YUV |
MAXIMUM |
przetwarzanie filmów i obrazów w aplikacji; | ||||
PRIV |
PREVIEW |
JPEG |
MAXIMUM |
Standardowe zdjęcia. | ||
YUV |
PREVIEW |
JPEG |
MAXIMUM |
Przetwarzanie w aplikacji i robienie zdjęć. | ||
PRIV |
PREVIEW |
PRIV |
PREVIEW |
Nagranie standardowe. | ||
PRIV |
PREVIEW |
YUV |
PREVIEW |
Podgląd i przetwarzanie w aplikacji. | ||
PRIV |
PREVIEW |
YUV |
PREVIEW |
JPEG |
MAXIMUM |
Przechwytywanie zdjęć i przetwarzanie w aplikacji. |
LEGACY to najniższy możliwy poziom sprzętowy. Z tej tabeli wynika, że każde urządzenie obsługujące Camera2 (API na poziomie 21 lub wyższym) może przesyłać do 3 strumieni jednocześnie, jeśli używa odpowiedniej konfiguracji i nie ma zbyt wielu czynników ograniczających wydajność, takich jak ograniczenia pamięci, procesora lub temperatury.
Aplikacja musi też skonfigurować bufory wyjściowe kierowania. Aby na przykład kierować reklamy na urządzenie o poziomie sprzętowym LEGACY, możesz skonfigurować 2 docelowe powierzchnie wyjściowe – jedną z użyciem ImageFormat.PRIVATE, a drugą z użyciem ImageFormat.YUV_420_888. Ta kombinacja jest obsługiwana w przypadku rozmiaru PREVIEW. Aby uzyskać wymagane rozmiary podglądu dla identyfikatora kamery, użyj funkcji zdefiniowanej wcześniej w tym dokumencie i tego 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 to poczekania, aż obiekt SurfaceView będzie gotowy, przy użyciu 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 SurfaceView do rozmiaru obrazu z kamery, wywołując SurfaceHolder.setFixedSize(), lub zastosować podejście podobne do AutoFitSurfaceView z modułu Common w przykładowych kodach kamery na GitHubie, które ustawia rozmiar bezwzględny, biorąc pod uwagę zarówno format obrazu, jak i dostępne miejsce, a jednocześnie automatycznie dostosowując się do zmian aktywności.
Konfigurowanie drugiej platformy z ImageReader z wybranym formatem jest łatwiejsze, ponieważ nie trzeba czekać na wywołania zwrotne:
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ć klatki 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);
Poziom sprzętu LEGACY jest przeznaczony dla urządzeń o najniższym wspólnym mianowniku. Możesz dodać rozgałęzienie warunkowe i użyć rozmiaru RECORD dla jednej z wyjściowych powierzchni docelowych na urządzeniach o poziomie sprzętowym LIMITED, a nawet zwiększyć go do rozmiaru MAXIMUM na urządzeniach o poziomie sprzętowym FULL.