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:
CameraCaptureSession.capture
) "directly" from any thread (including main thread
). Here we need to manage the session state and syncrhonize access to the Camera2 API.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?
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.