Примечание. На этой странице рассматривается пакет Camera2. Если вашему приложению не требуются определенные низкоуровневые функции Camera2, рекомендуем использовать CameraX. CameraX и Camera2 поддерживают Android 5.0 (уровень API 21) и более поздние версии.
На устройствах Android ориентация камеры и предварительного просмотра не всегда совпадает.
Камера находится в фиксированном положении на устройстве, независимо от того, является ли устройство телефоном, планшетом или компьютером. При изменении ориентации устройства ориентация камеры меняется.
В результате приложения камеры обычно предполагают фиксированную связь между ориентацией устройства и соотношением сторон предварительного просмотра камеры. Если телефон находится в портретной ориентации, то высота изображения в окне предварительного просмотра будет больше его ширины. Когда телефон (и камера) повернуты в горизонтальное положение, предварительный просмотр камеры должен быть шире, чем высота.
Однако эти предположения неверны для новых форм-факторов, таких как складные устройства, и режимов отображения, например многооконного и многоэкранного. Складные устройства меняют размер дисплея и соотношение сторон без изменения ориентации. В многооконном режиме приложения камеры ограничены частью экрана, а предпросмотр масштабируется независимо от ориентации устройства. В режиме нескольких экранов можно использовать дополнительные экраны, ориентация которых может отличаться от ориентации основного экрана.
Ориентация камеры
В определении совместимости Android указано, что датчик изображения камеры должен быть ориентирован так, чтобы длинная сторона камеры совпадала с длинной стороной экрана. То есть, когда устройство находится в горизонтальной ориентации, камеры ДОЛЖНЫ снимать изображения в горизонтальной ориентации. Это относится как к устройствам с альбомной ориентацией по умолчанию, так и к устройствам с портретной ориентацией по умолчанию.
Такое расположение камеры относительно экрана позволяет максимально увеличить область просмотра видоискателя в приложении камеры. Кроме того, датчики изображения обычно выводят данные в альбомной ориентации, чаще всего с соотношением сторон 4:3.
Естественная ориентация датчика камеры – горизонтальная. На рисунке 1 датчик фронтальной камеры (направленной в ту же сторону, что и экран) повернут на 270 градусов относительно телефона, чтобы соответствовать документу определения совместимости Android.
Чтобы приложения могли получать информацию о повороте датчика, в 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()возвращает угол поворота устройства по часовой стрелке (с точки зрения пользователя). Используйте отрицательное значение в формуле выше.
Фронтальные камеры
Вот буфер изображения, созданный датчиком камеры на рисунке 2:
Буфер необходимо повернуть на 270 градусов против часовой стрелки, чтобы скорректировать ориентацию датчика (см. раздел Ориентация камеры выше):
Затем буфер поворачивается ещё на 90 градусов против часовой стрелки, чтобы учесть поворот устройства. В результате получается правильная ориентация предварительного просмотра камеры на рисунке 2:
Вот как выглядит камера, повернутая вправо в горизонтальную ориентацию:
Вот буфер изображения:
Чтобы скорректировать ориентацию датчика, буфер необходимо повернуть на 270 градусов против часовой стрелки:
Затем буфер поворачивается ещё на 270 градусов против часовой стрелки, чтобы учесть поворот устройства:
Основные камеры
Обычно у задних камер ориентация датчика составляет 90 градусов (если смотреть с задней стороны устройства). При ориентации предварительного просмотра камеры буфер изображения датчика поворачивается по часовой стрелке на величину поворота датчика (а не против часовой стрелки, как у фронтальных камер), а затем буфер изображения поворачивается против часовой стрелки на величину поворота устройства.
Вот буфер изображения с датчика камеры на рисунке 4:
Буфер необходимо повернуть на 90 градусов по часовой стрелке, чтобы скорректировать ориентацию датчика:
Затем буфер поворачивается на 270 градусов против часовой стрелки, чтобы учесть поворот устройства:
Соотношение сторон
Соотношение сторон экрана меняется при изменении ориентации устройства, а также при складывании и раскладывании складных устройств, изменении размера окон в многооконных средах и открытии приложений на дополнительных дисплеях.
Буфер изображений датчика камеры должен быть ориентирован и масштабирован в соответствии с ориентацией и соотношением сторон элемента интерфейса видоискателя, поскольку интерфейс динамически меняет ориентацию – с изменением ориентации устройства или без него.
Если на новых типах устройств, в многооконном режиме или при использовании нескольких экранов приложение предполагает, что ориентация предпросмотра камеры совпадает с ориентацией устройства (книжной или альбомной), то предпросмотр может быть ориентирован или масштабирован неправильно.
На рисунке 5 приложение ошибочно предположило, что устройство повернуто на 90 градусов против часовой стрелки, и повернуло изображение на тот же угол.
На рисунке 6 приложение не скорректировало соотношение сторон буфера изображений, чтобы правильно масштабировать его в соответствии с новыми размерами элемента интерфейса предпросмотра камеры.
Приложения камеры с фиксированной ориентацией обычно сталкиваются с проблемами на складных устройствах и других устройствах с большим экраном, таких как ноутбуки:
На рисунке 7 интерфейс приложения камеры повернут набок, потому что в приложении разрешена только портретная ориентация. Изображение в видоискателе ориентировано правильно относительно датчика камеры.
Режим портретной съемки с вставкой
Приложения камеры, которые не поддерживают многооконный режим (resizeableActivity="false") и имеют ограничения по ориентации (screenOrientation="portrait" или screenOrientation="landscape"), на устройствах с большим экраном могут открываться в режиме портретной съемки, чтобы предпросмотр камеры отображался правильно.
В режиме портретной съемки приложения, поддерживающие только вертикальную ориентацию, отображаются в ней, даже если соотношение сторон экрана соответствует горизонтальной ориентации. Приложения, поддерживающие только горизонтальную ориентацию, показываются в режиме леттербокс даже если соотношение сторон экрана соответствует вертикальной ориентации. Изображение с камеры поворачивается, чтобы соответствовать интерфейсу приложения, обрезается в соответствии с соотношением сторон предварительного просмотра камеры, а затем масштабируется, чтобы заполнить предварительный просмотр.
Режим портретной вставки активируется, когда соотношение сторон датчика изображения камеры и соотношение сторон основного действия приложения не совпадают.
На рисунке 8 приложение камеры, поддерживающее только портретную ориентацию, повернуто так, чтобы интерфейс отображался вертикально на экране ноутбука. Приложение отображается в режиме леттербокс из-за разницы в соотношении сторон между вертикальным приложением и горизонтальным экраном. Изображение предпросмотра камеры было повернуто, чтобы компенсировать поворот интерфейса приложения (из-за встроенного режима портретной съемки), а также обрезано и масштабировано, чтобы соответствовать портретной ориентации, что уменьшило зону обзора.
Поворот, кадрирование, масштабирование
Режим портретной вставки вызывается для приложения камеры, поддерживающего только портретную ориентацию, на экране с альбомным соотношением сторон:
Приложение в вертикальной ориентации с черными полосами по краям:
Изображение с камеры поворачивается на 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() сообщает поставщику, что поверхность больше не нужна и связанные с ней ресурсы можно освободить.
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)); }
SurfaceView
SurfaceView – это простой способ создать предварительный просмотр камеры, если он не требует обработки и не анимирован.
SurfaceView автоматически поворачивает буфер изображения датчика камеры в соответствии с ориентацией дисплея, учитывая как ориентацию датчика, так и поворот устройства. Однако буфер изображения масштабируется в соответствии с размерами SurfaceView без учета соотношения сторон.
Вам необходимо убедиться, что соотношение сторон буфера изображения соответствует соотношению сторон SurfaceView. Это можно сделать, масштабируя содержимое SurfaceView в методе onMeasure() компонента:
Исходный код computeRelativeRotation() приведен в разделе Относительный поворот ниже.
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); }
Подробнее о том, как реализовать SurfaceView в качестве предварительного просмотра камеры, читайте в статье Ориентация камеры.
TextureView
TextureView менее эффективен, чем SurfaceView, и требует больше работы, но TextureView дает вам максимальный контроль над предварительным просмотром камеры.
TextureView поворачивает буфер изображения датчика в зависимости от ориентации датчика, но не обрабатывает поворот устройства или масштабирование предварительного просмотра.
Масштабирование и поворот можно закодировать в преобразовании Matrix. Чтобы узнать, как правильно масштабировать и поворачивать TextureView, прочитайте статью Поддержка масштабируемых поверхностей в приложении камеры.
Относительное вращение
Относительный поворот датчика камеры – это величина поворота, необходимая для выравнивания выходных данных датчика камеры с ориентацией устройства.
Относительный поворот используется такими компонентами, как SurfaceView и TextureView, для определения коэффициентов масштабирования по осям X и Y для изображения предварительного просмотра. Также используется для указания поворота буфера изображения датчика.
Классы CameraCharacteristics и Surface позволяют рассчитать относительный поворот датчика камеры:
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; }
Показатели окон
Размер экрана не должен использоваться для определения размеров видоискателя камеры. Приложение камеры может работать в части экрана, в многооконном режиме на мобильных устройствах или в режиме произвольной формы в ChromeOS.
WindowManager#getCurrentWindowMetrics() (добавлен в API уровня 30) возвращает размер окна приложения, а не размер экрана. Методы библиотеки Jetpack WindowManager WindowMetricsCalculator#computeCurrentWindowMetrics() и WindowInfoTracker#currentWindowMetrics() обеспечивают аналогичную поддержку с обратной совместимостью до уровня API 14.
Поворот на 180 градусов
Поворот устройства на 180 градусов (например, из стандартной ориентации в перевернутую) не активирует обратный вызов onConfigurationChanged(). В результате изображение с камеры может быть перевернуто.
Чтобы определить поворот на 180 градусов, реализуйте DisplayListener и проверьте поворот устройства, вызвав Display#getRotation() в обратном вызове onDisplayChanged().
Эксклюзивные ресурсы
До Android 10 только верхний видимый объект activity в многооконном режиме находился в состоянии RESUMED. Это сбивало пользователей с толку, потому что
система не указывала, какое действие было возобновлено.
В Android 10 (уровень API 29) появилась возможность одновременной работы нескольких приложений, когда все видимые действия находятся в состоянии RESUMED. Видимые действия по-прежнему могут переходить в состояние PAUSED, если, например, поверх действия находится прозрачное действие или действие не может быть в фокусе, как в режиме "картинка в картинке" (см. раздел Поддержка режима "картинка в картинке").
Приложение, использующее камеру, микрофон или любой эксклюзивный или одноэлементный ресурс на уровне API 29 или выше, должно поддерживать одновременную работу нескольких приложений. Например, если три возобновленных действия хотят использовать камеру, только одно из них сможет получить доступ к этому эксклюзивному ресурсу. Каждое действие должно реализовывать обратный вызов onDisconnected(), чтобы отслеживать упреждающий доступ к камере со стороны действия с более высоким приоритетом.
Подробнее об одновременной работе нескольких приложений…
Дополнительные ресурсы
- Пример использования Camera2 можно найти в приложении Camera2Basic на GitHub.
- Подробнее о том, как реализовать предварительный просмотр с помощью CameraX, рассказывается в статье Реализация предварительного просмотра.
- Пример реализации предварительного просмотра камеры CameraX можно найти в репозитории CameraXBasic на GitHub.
- Информацию о предварительном просмотре камеры в ChromeOS можно найти в разделе Ориентация камеры.
- Подробнее о разработке для складных устройств…