Search code examples
androidandroid-cameraandroid-camera2android-orientation

Getting relative rotation from the camera sensor to the current device orientation


There are a couple of samples to get the relative rotation from the camera sensor to the current device orientation, e.g. for using it for correcting camera preview or for MediaRecorder.setOrientationHint(int)

But the newest method from the latest official github camera samples works different than older methods (for deprecated camera2 API and method from archived camera2 sample)

Here's my sample code that uses all methods and logs all results, we can see that function computeRelativeRotationCamera2New returns different result for Surface.ROTATION_90 and Surface.ROTATION_270 display rotation

So what is the correct method to do it?

class MainOrientation : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val names = listOf(
            "Surface.ROTATION_0",
            "Surface.ROTATION_90",
            "Surface.ROTATION_180",
            "Surface.ROTATION_270"
        )

        listOf( // test all possible device rotation
            Surface.ROTATION_0,
            Surface.ROTATION_90,
            Surface.ROTATION_180,
            Surface.ROTATION_270
        ).forEachIndexed { idx, displayRotation ->
            // get related orientation (between device orientation and its camera sensor)
            // for example for MediaRecorder.setOrientationHint(int)

            Log.d(TAG, "Display Surface Rotation is ${names[idx]}")
            Log.d(TAG, " ")

            // two different methods for camera2 API (based on Google GitHub samples)
            computeRelativeRotationCamera2New(this, displayRotation) // from current camera2 samples https://github.com/android/camera-samples
            computeRelativeRotationCamera2(this, displayRotation) // from archived camera2 sample https://github.com/googlearchive/android-Camera2Basic

            // deprecated camera API (based on Android Camera API documentation)
            computeRelativeRotationCameraDeprecated(displayRotation) // https://developer.android.com/reference/android/hardware/Camera.html

            Log.d(TAG, "-------------------------")

            /* results from logcat:

            Display Surface Rotation is Surface.ROTATION_0

            computeRelativeRotationCamera2New 90
            computeRelativeRotationCamera2 90
            computeRelativeRotationCameraDeprecated 90
            -------------------------
            Display Surface Rotation is Surface.ROTATION_90

            computeRelativeRotationCamera2New 180
            computeRelativeRotationCamera2 0
            computeRelativeRotationCameraDeprecated 0
            -------------------------
            Display Surface Rotation is Surface.ROTATION_180

            computeRelativeRotationCamera2New 270
            computeRelativeRotationCamera2 270
            computeRelativeRotationCameraDeprecated 270
            -------------------------
            Display Surface Rotation is Surface.ROTATION_270

            computeRelativeRotationCamera2New 0
            computeRelativeRotationCamera2 180
            computeRelativeRotationCameraDeprecated 180
            */
        }
    }

    /**
     * Computes rotation required to transform from the camera sensor orientation to the
     * device's current orientation in degrees.
     *
     * @param characteristics the [CameraCharacteristics] to query for the sensor orientation.
     * @param surfaceRotation the current device orientation as a Surface constant
     * @return the relative rotation from the camera sensor to the current device orientation.
     */
    // https://github.com/android/camera-samples/blob/master/CameraUtils/lib/src/main/java/com/example/android/camera/utils/OrientationLiveData.kt#L71
    // https://github.com/android/camera-samples/blob/master/Camera2Video/app/src/main/java/com/example/android/camera2/video/fragments/CameraFragment.kt#L273
    private fun computeRelativeRotationCamera2New(
        context: Context,
        surfaceRotation: Int
    ): Int {
        val characteristics = getCameraCharacteristics(context)

        val sensorOrientationDegrees =
            characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION)!!

        val deviceOrientationDegrees = when (surfaceRotation) {
            Surface.ROTATION_0 -> 0
            Surface.ROTATION_90 -> 90
            Surface.ROTATION_180 -> 180
            Surface.ROTATION_270 -> 270
            else -> 0
        }

        // Reverse device orientation for front-facing cameras
        val sign = if (characteristics.get(CameraCharacteristics.LENS_FACING) ==
            CameraCharacteristics.LENS_FACING_FRONT
        ) 1 else -1

        // Calculate desired JPEG orientation relative to camera orientation to make
        // the image upright relative to the device orientation
        val result = (sensorOrientationDegrees - (deviceOrientationDegrees * sign) + 360) % 360
        Log.d(TAG, "computeRelativeRotationCamera2New $result")
        return result
    }

    // https://github.com/googlearchive/android-Camera2Video/blob/master/kotlinApp/Application/src/main/java/com/example/android/camera2video/Camera2VideoFragment.kt#L479
    private fun computeRelativeRotationCamera2(context: Context, surfaceRotation: Int) {
        // for computeRelativeRotationCamera2()
        val SENSOR_ORIENTATION_DEFAULT_DEGREES = 90
        val SENSOR_ORIENTATION_INVERSE_DEGREES = 270
        val DEFAULT_ORIENTATIONS = SparseIntArray().apply {
            append(Surface.ROTATION_0, 90)
            append(Surface.ROTATION_90, 0)
            append(Surface.ROTATION_180, 270)
            append(Surface.ROTATION_270, 180)
        }
       val INVERSE_ORIENTATIONS = SparseIntArray().apply {
            append(Surface.ROTATION_0, 270)
            append(Surface.ROTATION_90, 180)
            append(Surface.ROTATION_180, 90)
            append(Surface.ROTATION_270, 0)
        }
        when (getCameraCharacteristics(context).get(CameraCharacteristics.SENSOR_ORIENTATION)!!) {
            SENSOR_ORIENTATION_DEFAULT_DEGREES ->
                Log.d(
                    TAG,
                    "computeRelativeRotationCamera2 ${DEFAULT_ORIENTATIONS.get(surfaceRotation)}"
                )
            SENSOR_ORIENTATION_INVERSE_DEGREES ->
                Log.d(
                    TAG,
                    "computeRelativeRotationCamera2 ${INVERSE_ORIENTATIONS.get(surfaceRotation)}"
                )
        }
    }

    // https://developer.android.com/reference/android/hardware/Camera.html#setDisplayOrientation%28int%29
    private fun computeRelativeRotationCameraDeprecated(
        surfaceRotation: Int
    ): Int {
        val cameraInfo = getCameraDeprecatedInfo()
        val degrees = when (surfaceRotation) {
            Surface.ROTATION_0 -> 0
            Surface.ROTATION_90 -> 90
            Surface.ROTATION_180 -> 180
            Surface.ROTATION_270 -> 270
            else -> 0
        }
        var result: Int
        if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
            result = (cameraInfo.orientation + degrees) % 360
            result = (360 - result) % 360 // compensate the mirror
        } else {  // back-facing
            result = (cameraInfo.orientation - degrees + 360) % 360
        }
        Log.d(TAG, "computeRelativeRotationCameraDeprecated $result")
        return result
    }

    companion object {
        // methods to get info about camera using deprecated camera and camera2 APIs

        private fun getCameraManager(context: Context) =
            context.getSystemService(Context.CAMERA_SERVICE) as CameraManager

        private fun getCameraId(manager: CameraManager): String {
            return manager.cameraIdList.first {
                val characteristics = manager.getCameraCharacteristics(it)
                val facing = characteristics.get(CameraCharacteristics.LENS_FACING)
                facing == CameraCharacteristics.LENS_FACING_BACK
            }
        }

        private fun getCameraCharacteristics(context: Context): CameraCharacteristics {
            val manager = getCameraManager(context)
            return manager.getCameraCharacteristics(getCameraId(manager))
        }

        private fun getCameraDeprecatedInfo(): Camera.CameraInfo {
            val cameraInfo = Camera.CameraInfo()
            Camera.getCameraInfo(Camera.CameraInfo.CAMERA_FACING_BACK, cameraInfo)
            return cameraInfo
        }

        private const val TAG = "orientation"
    }
}

Solution

  • Tested recorded videos with different methods and found out that the newest method from the latest sample is incorrect for landscape video recording, it plays videos upside down

    I decided to update old method that uses deprecated camera api to use camera2 api (works for 21+ API) and use it in my project

    fun computeRelativeRotation(
        context: Context,
        surfaceRotation: Int, // display rotation 
        cameraId: String
    ): Int {
        val characteristics = getCameraCharacteristics(context, cameraId)
    
        val sensorOrientationDegrees = characteristics.get(
            CameraCharacteristics.SENSOR_ORIENTATION
        )!!
    
        val degrees = when (surfaceRotation) {
            Surface.ROTATION_0 -> 0
            Surface.ROTATION_90 -> 90
            Surface.ROTATION_180 -> 180
            Surface.ROTATION_270 -> 270
            else -> 0
        }
        var result: Int
        val cameraFacing = characteristics.get(CameraCharacteristics.LENS_FACING)
        if (cameraFacing == CameraCharacteristics.LENS_FACING_FRONT) {
            result = (sensorOrientationDegrees + degrees) % 360
            result = (360 - result) % 360 // compensate the mirror
        } else {  // back-facing
            result = (sensorOrientationDegrees - degrees + 360) % 360
        }
        return result
    }
    
    private fun getCameraCharacteristics(
        context: Context,
        cameraId: String
    ): CameraCharacteristics {
        return getCameraManager(context).getCameraCharacteristics(cameraId)
    }
    
    private fun getCameraManager(context: Context) =
        context.getSystemService(Context.CAMERA_SERVICE) as CameraManager