Камера setDisplayOrientation() в портретном режиме Разрывы Соотношение сторон

Я пытаюсь, чтобы предварительный просмотр камеры работал правильно в портретном режиме, когда самой активности разрешено нормально менять ориентацию (т.е. не быть заблокированной для пейзажа).

Использование setDisplayOrientation() серьезно нарушает поведение превью.

Это может продемонстрировать Google собственный ApiDemos. Первое изображение основано на CameraPreview из версии android-17 проекта образца ApiDemos, где единственным изменением, которое я сделал, было удаление android:orientation="landscape" из этой записи активности в манифесте. Следующее изображение представляет собой скриншот того, что появляется на дисплее Nexus 4, работающего под управлением Android 4.2, при этом камера указала на 8,5-дюймовый квадрат бумаги:

Nexus 4 ApiDemos Preview, Portrait Mode

Что не очевидно из этого предварительного просмотра, так это то, что он поворачивается на 90 градусов. Носки, одетые в носок, которые вы видите в крайнем правом углу изображения, были фактически ниже квадрата.

Решение для этого, по крайней мере для ландшафтного режима, заключается в использовании setDisplayOrientation(). Но если вы измените внутренний класс Preview CameraPreview на mCamera.setDisplayOrientation(90);, вы получите следующее:

Nexus 4 ApiDemos Preview, Portrait Mode, with setDisplayOrientation(90)

Примечательно, что квадрат больше не квадрат.

В этом случае используется тот же размер SurfaceView, что и раньше. Я воспроизвел этот сценарий с каким-то другим кодом, вне ApiDemos, и я получаю то же поведение с TextureView в этом коде.

Я кратко подумал, что проблема может заключаться в том, что getSupportedPreviewSizes() может возвращать разные значения на основе setDisplayOrientation(), но быстрый тест предполагает, что это не так.

Кто-нибудь знает, как заставить setDisplayOrientation() работать в портретном режиме, не нарушая пропорции изображений, перенесенных на предварительный просмотр?

Спасибо!

Ответы

Ответ 1

Хорошо, я думаю, что понял это. Было несколько штук в пресловутой загадке, и я благодарю @kcoppock за понимание, которое помогло мне решить некоторые из этих проблем.

Во-первых, вам нужно учитывать ориентацию при определении размера предварительного просмотра. Например, getOptimalPreviewSize() из CameraPreview of ApiDemos не обращает внимания на ориентацию, просто потому, что их версия этого приложения имеет ориентацию, привязанную к ландшафту. Если вы хотите, чтобы ориентация плавала, вам нужно изменить целевой формат изображения для соответствия. Итак, где getOptimalPreviewSize() имеет:

double targetRatio=(double)width / height;

вам также необходимо:

if (displayOrientation == 90 || displayOrientation == 270) {
  targetRatio=(double)height / width;
}

где displayOrientation - значение от 0 до 360, что я определяю примерно из 100 строк какого-то серьезного уродливого кода, поэтому я обертываю все это в повторно используемый компонент, который я опубликую в ближайшее время. Этот код основан на setDisplayOrientation() JavaDocs и fooobar.com/questions/104416/....

(Кстати, если вы читаете это после 1 июля 2013 года, и вы не видите ссылку в этом ответе на этот компонент, напишите комментарий, чтобы напомнить мне, как я забыл)

Во-вторых, вы должны учитывать эту ориентацию дисплея при управлении соотношением сторон SurfaceView/TextureView, которое вы используете. Операция CameraPreview от ApiDemos имеет свой собственный Preview ViewGroup, который обрабатывает соотношение сторон, и вам нужно отменить соотношение сторон для использования в портрете:

    if (displayOrientation == 90
        || displayOrientation == 270) {
      previewWidth=mPreviewSize.height;
      previewHeight=mPreviewSize.width;
    }
    else {
      previewWidth=mPreviewSize.width;
      previewHeight=mPreviewSize.height;
    }

где displayOrientation - это то же значение (90 и 270 как портретный, так и обратный портрет соответственно), и обратите внимание, что я не пытался работать с обратным портретом или обратным пейзажем, поэтому может быть требуется большая настройка).

Третий - и тот, который я нахожу в ярости - вы должны запустить предварительный просмотр перед вызовом setPictureSize() на Camera.Parameters. В противном случае, как будто соотношение сторон изображения применяется к кадрам предварительного просмотра, закручивая вещи вверх.

Поэтому у меня был код, похожий на следующий:

Camera.Parameters parameters=camera.getParameters();
Camera.Size pictureSize=getHost().getPictureSize(parameters);

parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
parameters.setPictureSize(pictureSize.width, pictureSize.height);
parameters.setPictureFormat(ImageFormat.JPEG);

camera.setParameters(parameters);
camera.startPreview();

и это неправильно. Что вам действительно нужно:

Camera.Parameters parameters=camera.getParameters();
Camera.Size pictureSize=getHost().getPictureSize(parameters);

parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);

camera.setParameters(parameters);
camera.startPreview();

parameters=camera.getParameters();
parameters.setPictureSize(pictureSize.width, pictureSize.height);
parameters.setPictureFormat(ImageFormat.JPEG);
camera.setParameters(parameters);

Без этого изменения у меня получилось бы растянутое соотношение сторон, даже в ландшафте. Это не было проблемой для CameraPreview of ApiDemos, поскольку они не фотографировали, поэтому они никогда не вызывали setPictureSize().

Но пока, на Nexus 4, теперь у меня есть квадратные пропорции для портрета и пейзажа с плавающей ориентацией (т.е. не закрытой для пейзажа).

Я попытаюсь не забыть изменить этот вопрос, если я столкнусь с другими хаками, требуемыми другими устройствами, а также для ссылки на мои компоненты CameraView/CameraFragment, когда они будут выпущены.


ОБНОВЛЕНИЕ # 1

Ну, конечно, это не могло быть почти таким простым.: -)

Исправление проблемы, в которой setPictureSize() закручивает соотношение сторон, работает... пока вы не сделаете снимок. В этот момент предварительный просмотр переключается на неправильное соотношение сторон.

Одной из возможностей было бы ограничить изображения теми, у кого одинаковое (или очень близкое) соотношение сторон к просмотру, поэтому икота не существует. Мне не нравится этот ответ, так как пользователь должен иметь возможность получать любой размер изображения, предлагаемый камерой.

Вы можете ограничить объем ущерба тем, что:

  • Только обновление размера изображения и т.д. Camera.Parameters непосредственно перед съемкой
  • Возврат к предварительному снимку Camera.Parameters после съемки

Это все еще отражает неправильное соотношение сторон в моменты, когда изображение выполняется. Долгое решение для этого - я думаю, - временно заменить предварительный просмотр камеры неподвижным изображением (последний кадр предварительного просмотра) во время съемки. Я попробую это в конце концов.


UPDATE # 2: v0.0.1 теперь доступна моя библиотека CWAC-камеры, в которую вошли вышеупомянутые код.

Ответ 2

Хорошо, ребята, Я действительно сходил с ума последние две недели об этой проблеме f ***. так хорошо, мало что нужно знать:

  • иногда вы просто получаете черные границы, если панель действий или программные кнопки видны, возможно на некоторых устройствах, мой совет: * не волнует * или вы " сойду с ума, или вырезать превью, оба не хороши...

изменения на @CommonsWare просто идеальны, но не могут применяться непосредственно к CameraPreview из примеров уровня API 8 (ссылка ниже), поэтому вот diff:

для правильного соотношения сторон в портрете:
методы одинаковы, просто примените diff

public void setCamera(Camera camera, boolean portrait) {
    mCamera = camera;
    this.portrait = portrait;
    if (mCamera != null) {
        mSupportedPreviewSizes = mCamera.getParameters().getSupportedPreviewSizes();
        requestLayout();
    }
}

public void switchCamera(Camera camera, boolean portrait) throws IOException {
   setCamera(camera, portrait);
   camera.setPreviewDisplay(mHolder);       
   Camera.Parameters parameters = camera.getParameters();
   parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
   requestLayout();

   camera.setParameters(parameters);
}

в

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {…}

изменения:

int previewWidth = width;
int previewHeight = height;
if (mPreviewSize != null) {
      previewWidth = mPreviewSize.width;
      previewHeight = mPreviewSize.height;
}

:

if (portrait && mPreviewSize != null) {
      previewWidth = mPreviewSize.height;
      previewHeight = mPreviewSize.width;
}
else if (mPreviewSize != null){
      previewWidth = mPreviewSize.width;
      previewHeight = mPreviewSize.height;
}

в

private Size getOptimalPreviewSize(List<Size> sizes, int w, int h) {…}

изменения:

double targetRatio = (double) w / h;

в

double targetRatio = 1;
if (portrait) {
    targetRatio= (double) w / h;
}

наконец-то начальная камера для портретного режима с чем-то вроде:

try {
     mCamera = openFrontFacingCameraGingerbread();
     if (portraitMode) {
       mCamera.setDisplayOrientation(90);
     }
     preview.setCamera(mCamera, portraitMode);
} catch (Exception e) {
     e.printStackTrace();
     handleCameraUnavailable();
}

WUUHUUUU! Для меня, наконец, не проблема в предварительном просмотре, и никаких проблем с сохранением изображений. Поскольку мое местоположение активности заблокировано, я получаю ориентацию дисплея через OrientationChangedListener для сохранения изображений с правильной ориентацией.

используя что-то вроде:
http://www.androidzeitgeist.com/2013/01/fixing-rotation-camera-picture.html

Но они правильно просматриваются и сохраняются.

Надеюсь, что я могу помочь многим людям, есть исходный код из уровня API 8:
https://android.googlesource.com/platform/development/+/master/samples/ApiDemos/src/com/example/android/apis/graphics/CameraPreview.java где были применены изменения.

Ответ 3

Измените ориентацию камеры на своем коде, и вы должны setDisplayorientation() не getDisplayOrientation()

if (this.getResources().getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE) 
{   
  camera.setDisplayOrientation(0);

}