Camera2 API 访问同步

Camera2 API access synchronization

Camera2 API 允许我们指定我们接收包含 CameraDeviceCameraCaptureSession、[=19= 的回调的线程(传递 Handler 实例) ] 等。我们使用这些回调中的信息来配置捕获会话、创建捕获请求并获取捕获结果。但是,当用户通过 UI 控制相机配置(例如对焦、测光)时,他是从主线程进行的。在这里,我们开发人员有两个选择:

  1. 从任何线程(包括main thread)。这里我们需要管理会话状态并同步对 Camera2 的访问 API.
  2. 将所有 Camera2 API 调用移至“CameraThread”。每当我们需要访问 Camera2 API 时,使用 Handler 将消息发送到“CameraThread”。所以我们实际上只会从单线程(“CameraThread”)使用它。

请让我澄清一下我的意思。假设我们为 Camera2 API 回调创建了 HandlerThread

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

第一种方法:

CameraCaptureSession.StateCallback 在“CameraThread”上运行。

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

以下方法可能被用户在任何线程调用,包括“MainThread”,所以我们需要同步访问mSession.

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

Camera2Basic 示例中使用了这种方法。到目前为止,我还在使用第一种方法。

第二种方法:

CameraCaptureSession.StateCallback 像前面的例子一样在“CameraThread”上运行。

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

但是,我们不会“直接”访问 mSession,而是 post 向“CameraThread”发送消息。因此,我们在这里不需要同步,因为 mSession 仅从单线程访问。

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

第二种方法的好处是我们可以避免任何多线程问题。 作为示例,我们可以考虑 CameraCaptureSession.CaptureCallback 作为预览捕获请求。来自此类重复请求的回调非常频繁地触发,应该为 AF 和 AE 控制进行处理。第二种方法允许我们在这种情况下避免同步成本,我认为,可能稍微提高性能并降低能耗。

据我从 sources 了解到,CameraDeviceImplCameraCaptureSessionImpl 是线程安全的,所以这两种方法都是可以接受的。

但是,我想知道第二种方法是否可行,哪种方法更好?

它们都是可行的。 “更好”取决于一系列因素,例如代码库的大小,以及代码中有多少不同的地方想要使用会话和设备。

将回调发送到相机处理程序线程会产生一些小的开销,而且需要编写更多的样板文件,因此对于较小的应用程序,只需从您所在的任何线程进行调用并适当同步即可。

然而,随着您应用的复杂性增加,将与相机的所有交互 API 保持在单个线程中变得越来越有吸引力;不仅因为您不必显式同步,还因为如果与相机对象的每次交互都发生在同一个线程上,则更容易推断所有权、系统状态等。此外,由于某些相机 API 方法可能会阻塞很长时间,因此您真的不想将 UI 冻结那么久。因此将调用发送到另一个线程是有价值的。

因此,这是一些额外的样板文件 + 少量开销与无法将相机代码集中在一个地方以实现简单性和流畅性的权衡。