Примечание. Эта страница относится к пакету 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
.
КамераX
Библиотека Jetpack CameraX упрощает создание видоискателя камеры, который учитывает ориентацию датчика и вращение устройства.
Элемент макета PreviewView
создает предварительный просмотр камеры, автоматически настраивая ориентацию датчика, поворот устройства и масштабирование. PreviewView
поддерживает соотношение сторон изображения с камеры, применяя тип масштаба FILL_CENTER
, который центрирует изображение, но может обрезать его, чтобы оно соответствовало размерам PreviewView
. Чтобы разместить изображение с камеры в почтовом ящике, установите тип масштаба FIT_CENTER
.
Чтобы узнать основы создания предварительного просмотра камеры с помощью PreviewView
, см. Реализация предварительного просмотра .
Полный пример реализации см. в репозитории CameraXBasic
на GitHub.
КамераВидоискатель
Подобно варианту использования предварительного просмотра , библиотека 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)) }
Ява
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
в методе 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) }
Ява
@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
менее эффективен, чем SurfaceView
, и требует больше работы, но TextureView
дает вам максимальный контроль над предварительным просмотром с камеры.
TextureView
вращает буфер изображения датчика в зависимости от ориентации датчика, но не обрабатывает вращение устройства или масштабирование предварительного просмотра.
Масштабирование и вращение можно закодировать с помощью матричного преобразования. Чтобы узнать, как правильно масштабировать и вращать 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)!! // 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 }
Ява
/** * 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 только самая верхняя видимая активность в многооконной среде находилась в состоянии RESUMED
. Это сбивало пользователей с толку, поскольку система не указывала, какая деятельность была возобновлена.
В Android 10 (уровень API 29) появилось множественное возобновление, при котором все видимые действия находятся в состоянии RESUMED
. Видимые действия по-прежнему могут переходить в состояние PAUSED
, если, например, прозрачное действие находится поверх действия или действие не фокусируется, например, в режиме «картинка в картинке» (см. Поддержка «картинка в картинке» ).
Приложение, использующее камеру, микрофон или любой эксклюзивный или одноэлементный ресурс на уровне API 29 или выше, должно поддерживать множественное резюме. Например, если три возобновленных действия захотят использовать камеру, только один сможет получить доступ к этому эксклюзивному ресурсу. Каждое действие должно реализовывать обратный вызов onDisconnected()
чтобы быть в курсе приоритетного доступа к камере со стороны действия с более высоким приоритетом.
Дополнительную информацию см. в разделе Мультирезюме .
Дополнительные ресурсы
- Пример Camera2 см. в приложении Camera2Basic на GitHub.
- Дополнительные сведения о сценарии использования предварительного просмотра CameraX см. в разделе « Реализация предварительного просмотра CameraX».
- Пример реализации предварительного просмотра камеры CameraX см. в репозитории CameraXBasic на GitHub.
- Информацию о предварительном просмотре камеры в ChromeOS см. в разделе Ориентации камеры .
- Информацию о разработке для складных устройств см. в разделе Дополнительные сведения о складных устройствах .