Примечание. На этой странице рассматривается пакет Camera2. Если вашему приложению не требуются определенные низкоуровневые функции Camera2, рекомендуем использовать CameraX. Обе библиотеки поддерживают Android 5.0 (API уровня 21) и более поздние версии.
На устройствах Android ориентация камеры и предварительного просмотра не всегда совпадает.
Камера находится в фиксированном положении на устройстве, независимо от того, является ли устройство телефоном, планшетом или компьютером. При изменении ориентации устройства меняется и ориентация камеры.
В результате приложения камеры обычно предполагают фиксированную связь между ориентацией устройства и соотношением сторон предварительного просмотра камеры. Если телефон находится в портретной ориентации, предполагается, что высота изображения с камеры больше его ширины. Когда телефон (и камера) повернуты в горизонтальное положение, изображение в окне предварительного просмотра должно быть шире, чем выше.
Однако эти предположения не учитывают новые форм-факторы, такие как складные устройства, и режимы отображения, например многооконный и многоэкранный. Складные устройства меняют размер дисплея и соотношение сторон без изменения ориентации. В многооконном режиме приложения камеры ограничены частью экрана, а предпросмотр масштабируется независимо от ориентации устройства. Режим нескольких дисплеев позволяет использовать дополнительные дисплеи, которые могут иметь не такую ориентацию, как основной дисплей.
Ориентация камеры
В определении совместимости Android указано, что датчик изображения камеры должен быть ориентирован так, чтобы длинная сторона камеры совпадала с длинной стороной экрана. То есть, когда устройство находится в горизонтальной ориентации, камеры ДОЛЖНЫ делать снимки в горизонтальной ориентации. Это относится к устройствам как с горизонтальной, так и с вертикальной ориентацией.
Такое расположение камеры и экрана позволяет максимально увеличить область отображения видоискателя в приложении камеры. Кроме того, датчики изображения обычно выводят данные в альбомной ориентации, чаще всего с соотношением сторон 4:3.
Естественная ориентация датчика камеры – горизонтальная. На рисунке 1 датчик фронтальной камеры (направленной в ту же сторону, что и экран) повернут на 270 градусов относительно телефона, чтобы соответствовать документу Android Compatibility Definition.
Чтобы приложения могли получать информацию о повороте датчика, в API camera2 включена константа SENSOR_ORIENTATION. Для большинства телефонов и планшетов устройство сообщает об ориентации датчика 270 градусов для фронтальных камер и 90 градусов (точка обзора с задней стороны устройства) для задних камер, что выравнивает длинный край датчика по длинному краю устройства. Камеры ноутбуков обычно сообщают об ориентации датчика 0 или 180 градусов.
Поскольку датчики изображения камеры выводят данные (буфер изображения) в естественной ориентации датчика (альбомной), буфер изображения должен быть повернут на количество градусов, указанное в SENSOR_ORIENTATION, чтобы предварительный просмотр камеры отображался вертикально в естественной ориентации устройства. Для фронтальных камер поворот выполняется против часовой стрелки, а для основных – по часовой.
Например, для фронтальной камеры на рисунке 1 буфер изображения, созданный датчиком камеры, выглядит следующим образом:
Изображение необходимо повернуть на 270 градусов против часовой стрелки, чтобы ориентация предварительного просмотра соответствовала ориентации устройства:
Основная камера создаст буфер изображения с той же ориентацией, что и предыдущий буфер, но значение SENSOR_ORIENTATION будет равно 90 градусам. В результате буфер поворачивается на 90 градусов по часовой стрелке.
Поворот устройства
Поворот устройства – это количество градусов, на которое устройство повернуто относительно своего естественного положения. Например, если телефон находится в горизонтальной ориентации, то угол поворота устройства составляет 90 или 270 градусов в зависимости от направления поворота.
Чтобы изображение в режиме предварительного просмотра отображалось вертикально, буфер изображений датчика камеры должен быть повернут на то же количество градусов, что и устройство (в дополнение к градусам ориентации датчика).
Расчет ориентации
Правильная ориентация предварительного просмотра камеры учитывает ориентацию датчика и поворот устройства.
Общий поворот буфера изображения датчика можно рассчитать по следующей формуле:
rotation = (sensorOrientationDegrees - deviceOrientationDegrees * sign + 360) % 360
где sign – это 1 для фронтальных камер и -1 для основных камер.
Для фронтальных камер буфер изображения поворачивается против часовой стрелки (от естественной ориентации датчика). Для основных камер буфер изображения датчика поворачивается по часовой стрелке.
Выражение deviceOrientationDegrees * sign + 360 преобразует поворот устройства против часовой стрелки в поворот по часовой стрелке для задних камер (например, преобразует 270 градусов против часовой стрелки в 90 градусов по часовой стрелке). Операция деления по модулю масштабирует результат до значения менее 360 градусов (например, масштабирует поворот на 540 градусов до 180).
Различные API по-разному сообщают о повороте устройства:
Display#getRotation()– поворот устройства против часовой стрелки (с точки зрения пользователя). Это значение подставляется в формулу как есть.OrientationEventListener#onOrientationChanged()возвращает угол поворота устройства по часовой стрелке (с точки зрения пользователя). Отрицательное значение для использования в предыдущей формуле.
Фронтальные камеры
Вот буфер изображения, созданный датчиком камеры на рисунке 4:
Буфер необходимо повернуть на 270 градусов против часовой стрелки, чтобы скорректировать ориентацию датчика (см. раздел Ориентация камеры выше):
Затем буфер поворачивается ещё на 90 градусов против часовой стрелки, чтобы учесть поворот устройства. В результате получается правильная ориентация предварительного просмотра камеры, как показано на рисунке 4.
Вот как выглядит камера, повернутая вправо в горизонтальную ориентацию:
Вот буфер изображения:
Буфер необходимо повернуть на 270 градусов против часовой стрелки, чтобы скорректировать ориентацию датчика:

Затем буфер поворачивается ещё на 270 градусов против часовой стрелки, чтобы учесть поворот устройства:

Основные камеры
Обычно у задних камер ориентация датчика составляет 90 градусов (если смотреть с задней стороны устройства). При ориентации предварительного просмотра камеры буфер изображения датчика поворачивается по часовой стрелке на величину поворота датчика (а не против часовой стрелки, как у фронтальных камер), а затем буфер изображения поворачивается против часовой стрелки на величину поворота устройства.
На рисунке 12 показан буфер изображений от датчика камеры.

Буфер необходимо повернуть на 90 градусов по часовой стрелке, чтобы скорректировать ориентацию датчика:

Затем буфер поворачивается на 270 градусов против часовой стрелки, чтобы учесть поворот устройства:

Соотношение сторон
Соотношение сторон экрана меняется при изменении ориентации устройства, а также при складывании и раскладывании складных устройств, при изменении размера окон в многооконных средах и при открытии приложений на дополнительных дисплеях.
Буфер изображений датчика камеры должен быть ориентирован и масштабирован в соответствии с ориентацией и соотношением сторон элемента интерфейса видоискателя, поскольку интерфейс динамически меняет ориентацию – с изменением ориентации устройства или без него.
Если на новых типах устройств, в многооконном режиме или при использовании нескольких экранов приложение предполагает, что ориентация предпросмотра камеры совпадает с ориентацией устройства (книжная или альбомная), то предпросмотр может отображаться неправильно, иметь неверный масштаб или и то, и другое.

На рисунке 16 приложение ошибочно предположило, что устройство повернуто на 90 градусов против часовой стрелки, и повернуло изображение на тот же угол.

На рисунке 17 приложение не скорректировало соотношение сторон буфера изображения, чтобы оно соответствовало новым размерам элемента интерфейса предпросмотра камеры.
Приложения камеры с фиксированной ориентацией обычно сталкиваются с проблемами на складных устройствах и других устройствах с большим экраном, таких как ноутбуки:

На рисунке 18 интерфейс приложения камеры повернут набок, поскольку ориентация приложения ограничена только портретным режимом. Изображение в видоискателе ориентировано правильно относительно датчика камеры.
Режим портретной съемки с вставкой
Приложения камеры, которые не поддерживают многооконный режим (resizeableActivity="false") и ограничивают ориентацию (screenOrientation="portrait" или screenOrientation="landscape"), можно перевести в режим портретной съемки на устройствах с большим экраном, чтобы правильно ориентировать предпросмотр камеры.
В режиме портретной съемки приложения, поддерживающие только вертикальную ориентацию, показываются в ней, даже если соотношение сторон экрана соответствует горизонтальной ориентации. Приложения, поддерживающие только горизонтальную ориентацию, показываются в ней с черными полосами по краям, даже если соотношение сторон экрана соответствует вертикальной ориентации. Изображение с камеры поворачивается, чтобы соответствовать интерфейсу приложения, обрезается в соответствии с соотношением сторон предварительного просмотра камеры, а затем масштабируется, чтобы заполнить предварительный просмотр.
Режим портретной вставки активируется, когда соотношение сторон датчика изображения камеры и соотношение сторон основного действия приложения не совпадают.

На рисунке 19 приложение камеры, предназначенное только для портретной ориентации, было повернуто, чтобы отобразить пользовательский интерфейс в вертикальном положении на экране ноутбука. Приложение отображается в режиме леттербокс из-за разницы в соотношении сторон между вертикальным приложением и горизонтальным экраном. Изображение в окне предварительного просмотра камеры было повернуто, чтобы компенсировать поворот интерфейса приложения (из-за встроенного режима портретной съемки), а также обрезано и масштабировано в соответствии с портретной ориентацией, что уменьшило зону обзора.
Поворот, кадрирование, масштабирование
Режим портретной вставки вызывается для приложения камеры, поддерживающего только портретную ориентацию, на экране с альбомным соотношением сторон:

Приложение в вертикальной ориентации с черными полосами по краям:
Изображение с камеры поворачивается на 90 градусов, чтобы соответствовать новой ориентации приложения.

Изображение обрезается до соотношения сторон предварительного просмотра камеры, а затем масштабируется, чтобы заполнить область предварительного просмотра (зона обзора уменьшается):
На складных устройствах ориентация датчика камеры может быть вертикальной, а соотношение сторон экрана – горизонтальным.
Поскольку изображение в окне предварительного просмотра поворачивается в соответствии с ориентацией датчика, в видоискателе оно отображается правильно, но приложение, поддерживающее только портретную ориентацию, повернуто набок.
В режиме портретной вставки кашетирование приложения требуется только в портретной ориентации, чтобы правильно ориентировать приложение и предварительный просмотр камеры:
API
Начиная с Android 12 (уровень API 31) приложения могут явно управлять режимом вертикальной ориентации с помощью свойства SCALER_ROTATE_AND_CROP класса CaptureRequest.
Значение по умолчанию – SCALER_ROTATE_AND_CROP_AUTO. Оно позволяет системе вызывать режим портретной съемки.
SCALER_ROTATE_AND_CROP_90 – это поведение в режиме портретной съемки, описанное выше.
Не все устройства поддерживают все значения SCALER_ROTATE_AND_CROP. Список поддерживаемых значений приведен в разделе CameraCharacteristics#SCALER_AVAILABLE_ROTATE_AND_CROP_MODES.
CameraX
Библиотека Jetpack CameraX позволяет легко создать видоискатель, который учитывает ориентацию датчика и поворот устройства.
Элемент макета PreviewView создает окно предварительного просмотра камеры, автоматически подстраиваясь под ориентацию датчика, поворот устройства и масштабирование.
PreviewView сохраняет соотношение сторон изображения с камеры, применяя тип масштабирования FILL_CENTER, который центрирует изображение, но может обрезать его в соответствии с размерами PreviewView. Чтобы добавить черные полосы по краям изображения с камеры, задайте тип масштабирования FIT_CENTER.
Чтобы узнать, как создать предварительный просмотр с камеры с помощью PreviewView, прочитайте статью Как реализовать предварительный просмотр.
Полный пример реализации можно найти в репозитории CameraXBasic на GitHub.
CameraViewfinder
Как и в случае с предварительным просмотром, библиотека CameraViewfinder предоставляет набор инструментов, упрощающих создание предварительного просмотра камеры. Она не зависит от CameraX Core, поэтому вы можете легко интегрировать ее в существующую базу кода Camera2.
Вместо того чтобы использовать Surface напрямую, вы можете использовать виджет CameraViewfinder, чтобы показывать видеопоток с камеры для Camera2.
Для показа видеопотока с камеры CameraViewfinder использует TextureView или SurfaceView, а также применяет к ним необходимые преобразования, чтобы правильно отображать видоискатель. Это включает в себя исправление их соотношения сторон,
масштаба и поворота.
Чтобы запросить поверхность у объекта CameraViewfinder, необходимо создать ViewfinderSurfaceRequest.
Этот запрос содержит требования к разрешению поверхности и информацию о камере из CameraCharacteristics.
Вызов requestSurfaceAsync() отправляет запрос поставщику поверхности, который является либо TextureView, либо SurfaceView, и получает ListenableFuture из Surface.
Вызов markSurfaceSafeToRelease() уведомляет поставщика поверхности о том, что поверхность не нужна и связанные с ней ресурсы могут быть освобождены.
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))
}
SurfaceView
SurfaceView – это простой способ создать предварительный просмотр камеры, если он не требует обработки и не анимирован.
SurfaceView автоматически поворачивает буфер изображения датчика камеры в соответствии с ориентацией экрана, учитывая как ориентацию датчика, так и поворот устройства.
Однако буфер изображения масштабируется в соответствии с размерами SurfaceView без учета соотношения сторон.
Вам необходимо убедиться, что соотношение сторон буфера изображения соответствует соотношению сторон SurfaceView. Этого можно добиться, масштабируя содержимое SurfaceView в методе onMeasure() компонента:
Исходный код computeRelativeRotation() приведен в разделе Относительный поворот ниже.
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) }
Подробнее о том, как реализовать SurfaceView в качестве предварительного просмотра камеры, читайте в статье Ориентация камеры.
TextureView
TextureView менее эффективен, чем SurfaceView, и требует больше работы, но TextureView дает вам максимальный контроль над предварительным просмотром камеры.
TextureView поворачивает буфер изображения датчика в зависимости от ориентации датчика, но не обрабатывает поворот устройства или масштабирование предварительного просмотра.
Масштабирование и поворот можно закодировать в преобразовании Matrix.
Чтобы узнать, как правильно масштабировать и поворачивать TextureView, прочитайте статью Поддержка изменяемых поверхностей в приложении камеры.
Относительное вращение
Относительный поворот датчика камеры – это величина поворота, необходимая для выравнивания выходных данных датчика камеры с ориентацией устройства.
Относительный поворот используется такими компонентами, как SurfaceView и TextureView, для определения коэффициентов масштабирования по осям X и Y для изображения предварительного просмотра. Он также используется для указания поворота буфера изображения датчика.
Классы CameraCharacteristics и Surface позволяют рассчитать относительный поворот датчика камеры:
/** * 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) ?: 0 // 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 }
Показатели окон
Размер экрана не должен использоваться для определения размеров видоискателя камеры. Приложение камеры может работать в части экрана, в многооконном режиме на мобильных устройствах или в режиме произвольной формы в ChromeOS.
WindowManager#getCurrentWindowMetrics() (добавлен в API уровня 30) возвращает размер окна приложения, а не размер экрана. Методы библиотеки Jetpack WindowManager WindowMetricsCalculator#computeCurrentWindowMetrics() и WindowInfoTracker#currentWindowMetrics() обеспечивают аналогичную поддержку с обратной совместимостью до уровня API 14.
Поворот на 180 градусов
Поворот устройства на 180 градусов (например, из стандартной ориентации в перевернутую) не запускает обратный вызов onConfigurationChanged(). В результате изображение с камеры может быть перевернуто.
Чтобы определить поворот на 180 градусов, реализуйте DisplayListener и проверьте поворот устройства с помощью вызова Display#getRotation() в обратном вызове onDisplayChanged().
Эксклюзивные ресурсы
До Android 10 только верхнее видимое действие в многооконном режиме находилось в состоянии RESUMED. Это сбивало пользователей с толку, поскольку система не указывала, какое действие было возобновлено.
В Android 10 (уровень API 29) появилась возможность одновременной работы нескольких приложений, когда все видимые объекты activity находятся в состоянии RESUMED. Видимые действия могут перейти в состояние PAUSED, если, например, поверх них находится прозрачное действие или они не могут быть в фокусе, как в режиме "Картинка в картинке" (см. раздел Поддержка режима "Картинка в картинке").
Приложение, использующее камеру, микрофон или любой эксклюзивный или одноэлементный ресурс на уровне API 29 или выше, должно поддерживать одновременную работу нескольких приложений. Например, если три возобновленных действия хотят использовать камеру, только одно из них сможет получить доступ к этому эксклюзивному ресурсу. Каждое действие должно реализовывать обратный вызов onDisconnected(), чтобы отслеживать упреждающий доступ к камере со стороны действия с более высоким приоритетом.
Подробнее об одновременной работе нескольких приложений…
Дополнительные ресурсы
- Пример использования Camera2 можно найти в приложении Camera2Basic на GitHub.
- Подробнее о том, как реализовать предварительный просмотр с помощью CameraX, рассказывается в документации.
- Пример реализации предварительного просмотра камеры CameraX можно найти в репозитории CameraXBasic на GitHub.
- Информацию о предварительном просмотре камеры в ChromeOS можно найти в разделе Ориентация камеры.
- Подробнее о разработке для складных устройств…