Androidデバイスのカメラの向き


基準となる向き

デバイスには基準となる向きがある。
機種によって異なるが、ハンドセットではポートレートの上が、横置きが標準のタブレットではランドスケープの上が基準となる。
加速度センサーの値はこの基準と同じものが返る。


アプリの向き

WindowManager#getDefaultDisplay() から取得できる。

WindowManager windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
int rotation = windowManager.getDefaultDisplay().getRotation();
switch (rotation) {
    case Surface.ROTATION_0:
        // アプリの上がデバイスの基準と同じになる(緑の矢印の方向)
        break;
    case Surface.ROTATION_90:
        // アプリの上がデバイスの基準から90度回転したものになる(赤の矢印の方向)
        break;
    case Surface.ROTATION_180:
        // アプリの上がデバイスの基準から180度回転したものになる(緑の矢印の逆の方向)
        break;
    case Surface.ROTATION_270:
        // アプリの上がデバイスの基準から270度回転したものになる(赤の矢印の逆の方向)
        break;
    default:
        // ありえない
        break;
}

getResources().getConfiguration().orientation でも似たようなものが取得できるが、こちらは飽くまでLANDSCAPE / PORTRAIT / SQUARE などであり、方向ではない、

カメラの向き

カメラのSensorOrientationから角度が取得できる。多くのデバイスは90度(赤の矢印の方向)についている。Nexus5Xに代表される一部のデバイスは270度(赤の矢印の逆の方向)で取り付けられている。

Camera 1 API(旧Camera API)

Camera.CameraInfo info = new Camera.CameraInfo();
Camera.getCameraInfo(CameraMetadata.LENS_FACING_BACK, info);
// info.orientation が0, 90, 180, 270などの角度になっている

Camera 2 API

CameraManager cameraManager = (CameraManager) getSystemService(CAMERA_SERVICE);
CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraId);
Integer sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
// sensorOrientation が0, 90, 180, 270などの角度になっている

アプリに画像を引き込むとき

何もしないとカメラがキャプチャした画像はアプリの向きとは異なっている。このためそのままのTextureViewやImageViewに表示するとアプリの向きとカメラの向きが食い違っているとズレることになる。

Camera 1 APIのプレビュー向きを調整するアプローチ

Camera.setDisplayOrientation(int)を使う。

Camera2 APIのTextureViewでのプレビュー

アプリの向きを使ってTextureViewのMatrixで調整する。
(カメラの向きについてはAPI側で回転してくれている?)

WindowManager windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
int rotation = windowManager.getDefaultDisplay().getRotation();
int viewWidth = textureView.getWidth();
int viewHeight = textureView.getHeight();
Matrix matrix = new Matrix();
matrix.postRotate(- rotation, viewWidth * 0.5f, viewHeight * 0.5f);
textureView.setTransform(matrix);

サンプルアプリの Camera2BasicFragment#openCamera(int width, int height) も参考になる。

Camera2 APIのImageReaderでの画像のキャプチャ

API側で何も回転させていないため、アプリの向きとカメラの向きから調整する。

Camera2BasicFragment#getOrientation が参考になる。

int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getOrientation(rotation));

向きの確認用コード

スニペットをまとめているアプリにアプリやカメラの向きを確認するコードを追加しました。

まとめ

Camera1 APIとCamera2 APIのどちらもアプリの向きとカメラの向きを意識する必要がある。
だけどAPI側で部分的に回転をしてくれてるところがあるので、どこまでやってくれているかを正しく認識していないと混乱しやすい。
鍵となるアプリの向きとカメラの向きを取る手段は提供されているので、それを使って調整することが肝になる。

大抵のデバイスのカメラの向きは90度だからといって、それで固定するとそれ以外のデバイスで表示がおかしくなるので避けること。
それでも本当におかしいデバイスについては仕方がないのでデバイスIDで対応する。