Search code examples
androidcameraandroid-cameraandroid-camera2

Camera2 API access synchronization


Camera2 API allows us to specify the thread (passing the Handler instance) on which we recieve the callbacks containing CameraDevice, CameraCaptureSession, CaptureResult etc. We use the information from these callbacks to configure capture session, create capture requests and obtain capture results. However, when the user controls the camera configuration (e.g. focusing, metering) through the UI, he makes it from the main thread. Here we, developers, have two options:

  1. Use the Camera2 API calls (e.g. CameraCaptureSession.capture) "directly" from any thread (including main thread). Here we need to manage the session state and syncrhonize access to the Camera2 API.
  2. Move all Camera2 API calls to the "CameraThread". Send the message to the "CameraThread" using Handler whenever we need access to Camera2 API. So we will actually use it only from the single thread ("CameraThread").

Please, let me clarify what I mean. Suppose that we created HandlerThread for Camera2 API callbacks.

mCameraThread = new HandlerThread("CameraThread");
mCameraThread.start();
mHandler = new Handler(mCameraThread.getLooper());

The first approach:

CameraCaptureSession.StateCallback runs on "CameraThread".

public void onConfigured(@NonNull CameraCaptureSession session) {
    synchronized (STATE_MONITOR){
        mState = State.Configured;
        mSession = session;
    }
}

The following method may be called by user on any thread, including "MainThread", so we need to synchronize the access to mSession.

public void takePicture() {
    synchronized (STATE_MONITOR){
        if(mState == State.Configured){
            CaptureRequest request = ...;
            mSession.capture(request, callback, mHandler)
        }
    }
}

This approach is used in Camera2Basic example. So far, I am still using the first approach also.

The second approach:

CameraCaptureSession.StateCallback runs on "CameraThread" as in the previous case.

public void onConfigured(@NonNull CameraCaptureSession session) {
    mState = State.Configured;
    mSession = session;
}

However, we do not access mSession "directly", instead, we post message to the "CameraThread". Therefore, we do not need synchronization here, since the mSession is accessed only from the single thread.

public void takePicture() {
    assertThatThreadIsRunning();
    mHandler.post(() -> {
        if(mState == State.Configured){
            CaptureRequest request = ...;
            mSession.capture(request, callback, mHandler)
        }
    });
}

The benefit from the second approach is that we can avoid any multithreading cares. As an example we can consider CameraCaptureSession.CaptureCallback for the preview capture request. The callbacks from such repeating request triggers very frequently and should be processed for the AF and AE control. The second approach allows us to avoid the synchronization costs in this case, which, I think, may slightly improve performance and reduce energy consumption.

As far as I got from the sources, theCameraDeviceImpl and CameraCaptureSessionImpl are threadsafe, so the both approaches are acceptable.

However, I would like to know If the second approach is viable and which one is better?


Solution

  • They're both viable. "Better" depends on a bunch of factors such as the size of the codebase, and how many different places in the code will be wanting to use the session and device.

    There's some minor overhead in sending the callback to the camera handler thread, plus more boilerplate to write, so for smaller apps, just making calls from whatever thread you're in and synchronizing appropriately works fine.

    However, as your app's complexity grows, it starts becoming attractive to keep all interaction with the camera API to a single thread; not just because you don't have to synchronize explicitly, but because it's easier to reason about ownership, the state of the system, and so on, if every interaction with the camera object happens on the same thread. Also, since some of the camera API methods can block for extended time periods, you really don't want to freeze your UI for that long anyway. So sending the calls to another thread is valuable.

    So it's a tradeoff of some extra boilerplate + minor overhead vs. inability to centralize camera code in one place for simplicity and smoothness.