Android Camera2 Tutorial. Chapter 2. Switching Cameras

Posted by n14charlie on Tue, 18 Jun 2019 22:05:13 +0200

Previous Chapter Overview of Camera2 Here we have introduced some of Camera2's basics, but not many API s. Starting with this chapter, we will develop an application with full camera functionality and divide the camera knowledge into several chapters. This chapter is about the camera opening process.

After reading this chapter, you will learn the following points:

  1. How to register camera-related permissions
  2. How to configure camera feature requirements
  3. How to turn on the camera
  4. How to turn off the camera

You can do it at https://github.com/darylgo/Camera2Sample Download the relevant source code and switch to the Tutorial2 tab.

1 Create Camera Project

As mentioned earlier, we will develop an application with full camera functionality, so the first step is to create a camera project, where I created a project called Camera2Sample with AS, and there is an Activity called MainActivity.The development language we use is Kotlin, so if you are not familiar with Kotlin, it is recommended that you first learn the basics of Kotlin.

To reduce the difficulty of reading the source code, I do not intend to introduce any third-party libraries, focus on performance issues, or design in any mode. Most of the code will be written in this MainActivity, and all the functions will be implemented as easily as possible so that readers can focus only on the focus.

2 Register related permissions

Before you can use the camera API, you must register the camera rights android.permission.CAMERA in AndroidManifest.xml, stating that the applications we develop require camera rights, and if you have operations to save photos, read and write SD card rights are also required:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.darylgo.camera.sample">

    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

</manifest>

It is important to note that systems above 6.0 require us to apply for dynamic permissions while the program is running, so we need to check permissions when the program starts. When any necessary permissions are denied by the user, we pop-up window prompts the user that the program cannot function properly because permissions are denied:

class MainActivity : AppCompatActivity() {

    companion object {
        private const val REQUEST_PERMISSION_CODE: Int = 1
        private val REQUIRED_PERMISSIONS: Array<String> = arrayOf(
                android.Manifest.permission.CAMERA,
                android.Manifest.permission.WRITE_EXTERNAL_STORAGE
        )
    }

    /**
     * Determine if the privileges we need are granted. As long as one is not authorized, we will return false and apply for privileges.
     *
     * @return true Permissions are authorized
     */
    private fun checkRequiredPermissions(): Boolean {
        val deniedPermissions = mutableListOf<String>()
        for (permission in REQUIRED_PERMISSIONS) {
            if (ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_DENIED) {
                deniedPermissions.add(permission)
            }
        }
        if (deniedPermissions.isEmpty().not()) {
            requestPermissions(deniedPermissions.toTypedArray(), REQUEST_PERMISSION_CODE)
        }
        return deniedPermissions.isEmpty()
    }
}

3 Configure camera feature requirements

You don't want users to install your camera application on a phone that doesn't have any cameras, because that doesn't make sense.So the next thing you need to do is configure some camera features in AndroidManifest.xml that are necessary for the program to run. If these features are not supported, users will not be able to install the apk because the conditions do not meet.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.darylgo.camera.sample">

    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

    <uses-feature
        android:name="android.hardware.camera"
        android:required="true" />

</manifest>

With the <uses-feature>tag, we declare that our app must run on a mobile phone with a camera.You can also configure additional feature requirements, such as the need to support an autofocus camera to run your application. More features can be found in Official Documents Query above.

4 Get an instance of CameraManager

CameraManager is a system service responsible for querying and establishing camera connections. It can be said that CameraManager is the starting point of Camera2 usage process, so first we need to get an instance of CameraManager through getSystemService():

private val cameraManager: CameraManager by lazy { getSystemService(CameraManager::class.java) }

5 Get a list of camera ID s

Next we want to get a list of all available camera IDs, the length of which also represents how many cameras are available.The API used is CameraManager.getCameraIdList(), which returns an array of strings containing all available camera IDs:

val cameraIdList = cameraManager.cameraIdList

Note: Kotlin converts getter s from many Java API s directly into Kotlin's property syntax, so you'll see that getCameraIdList() is converted to cameraIdList, followed by many similar conversions, which are explained in advance to avoid misinterpretation.

6 Get CameraCharacteristics from Camera ID

CameraCharacteristics is the provider of camera information, through which we can get all camera information. Here we need to filter the front and rear cameras according to the direction of the camera, and require that the Hardware Level of the camera be FULL or above, so first we need to get all CameraCharacteristics instances of the cameras, the API involved is CameraManager.g.EtCameraCharacteristics(), which returns the corresponding camera information based on the camera ID you specify:

/**
 * Determines whether the camera's Hardware Level is greater than or equal to the specified Level.
 */
fun CameraCharacteristics.isHardwareLevelSupported(requiredLevel: Int): Boolean {
    val sortedLevels = intArrayOf(
            CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY,
            CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED,
            CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
            CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3
    )
    val deviceLevel = this[CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL]
    if (requiredLevel == deviceLevel) {
        return true
    }
    for (sortedLevel in sortedLevels) {
        if (requiredLevel == sortedLevel) {
            return true
        } else if (deviceLevel == sortedLevel) {
            return false
        }
    }
    return false
}
// Traverse through all available camera ID s, taking out only the front and rear camera information.
val cameraIdList = cameraManager.cameraIdList
cameraIdList.forEach { cameraId ->
    val cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraId)
    if (cameraCharacteristics.isHardwareLevelSupported(REQUIRED_SUPPORTED_HARDWARE_LEVEL)) {
        if (cameraCharacteristics[CameraCharacteristics.LENS_FACING] == CameraCharacteristics.LENS_FACING_FRONT) {
            frontCameraId = cameraId
            frontCameraCharacteristics = cameraCharacteristics
        } else if (cameraCharacteristics[CameraCharacteristics.LENS_FACING] == CameraCharacteristics.LENS_FACING_BACK) {
            backCameraId = cameraId
            backCameraCharacteristics = cameraCharacteristics
        }
    }
}

7 Turn on the camera

The next thing we need to do is call the CameraManager.openCamera() method to turn on the camera, which requires us to pass two parameters, one is the camera ID and the other is the CameraStateCallback, which monitors the camera's state.A CameraDevice instance will be called back to you by the CameraStateCallback.onOpened() method when the camera is successfully turned on, otherwise a CameraDevice instance and an error code will be called back by the CameraStateCallback.onError() method.Both onOpened () and onError () actually mean that the camera has been turned on. The only difference is that onError () indicates that something went wrong during the turn-on process. Instead of continuing to use it, you must turn off the ameraDevice that you passed to you. The specific API description allows you to view the documentation yourself.Also, you must make sure that you have granted camera privileges before you turn on the camera, or you will throw a privilege exception.A safer practice is to check camera permissions before turning on the camera each time.Here are the main code snippets:

private data class OpenCameraMessage(val cameraId: String, val cameraStateCallback: CameraStateCallback)

@SuppressLint("MissingPermission")
override fun handleMessage(msg: Message): Boolean {
    when (msg.what) {
        MSG_OPEN_CAMERA -> {
            val openCameraMessage = msg.obj as OpenCameraMessage
            val cameraId = openCameraMessage.cameraId
            val cameraStateCallback = openCameraMessage.cameraStateCallback
            cameraManager.openCamera(cameraId, cameraStateCallback, cameraHandler)
            Log.d(TAG, "Handle message: MSG_OPEN_CAMERA")
        }
    }
    return false
}

private fun openCamera() {
    // Limited choice of rear camera, followed by front camera.
    val cameraId = backCameraId ?: frontCameraId
    if (cameraId != null) {
        val openCameraMessage = OpenCameraMessage(cameraId, CameraStateCallback())
        cameraHandler?.obtainMessage(MSG_OPEN_CAMERA, openCameraMessage)?.sendToTarget()
    } else {
        throw RuntimeException("Camera id must not be null.")
    }
}
private inner class CameraStateCallback : CameraDevice.StateCallback() {
    @WorkerThread
    override fun onOpened(camera: CameraDevice) {
        cameraDevice = camera
        runOnUiThread { Toast.makeText(this@MainActivity, "Camera is on", Toast.LENGTH_SHORT).show() }
    }

    @WorkerThread
    override fun onError(camera: CameraDevice, error: Int) {
        camera.close()
        cameraDevice = null
    }
}

8 Turn off the camera

As with other hardware resources, remember to call the CameraDevice.close() method and turn off the camera to recycle resources when we no longer need to use the camera.The operation of turning off the camera is important because if you keep using the camera resources, other camera-based functions will not work properly, which in severe cases will directly cause other camera-related APP s to not work properly. When the camera is completely turned off, you will be notified that the camera has been turned off by the CameraStateCallback.onCllosed() method.So when is the best time to turn off the camera?My personal recommendation is to turn off the camera when onPause(), because at this time the camera page is no longer the focus of the user, and in most cases the camera can already be turned off.

@SuppressLint("MissingPermission")
override fun handleMessage(msg: Message): Boolean {
    when (msg.what) {
        MSG_CLOSE_CAMERA -> {
            cameraDevice?.close()
            Log.d(TAG, "Handle message: MSG_CLOSE_CAMERA")
        }
    }
    return false
}

override fun onPause() {
    super.onPause()
    closeCamera()
}

private fun closeCamera() {
    cameraHandler?.sendEmptyMessage(MSG_CLOSE_CAMERA)
}
private inner class CameraStateCallback : CameraDevice.StateCallback() {
    @WorkerThread
    override fun onClosed(camera: CameraDevice) {
        cameraDevice = null
        runOnUiThread { Toast.makeText(this@MainActivity, "Camera is off", Toast.LENGTH_SHORT).show() }
    }
}

This concludes the tutorial on switching cameras on and off, and in the next chapter we'll show you how to turn on the preview.

Topics: Mobile Android xml encoding Java