android.hardware.camera2 系列----CameraDevice

璩正志
2023-12-01

我们在之前的文章中介绍了 openCamera的流程,openCamera最终使CameraApp拿到了一个叫CameraDevice的对象,后面我们在创建会话、开启预览、拍照等流程中均看到这个CameraDvice的身影,今天我们来详细看这个在Camera中占据重要地位的对象:CameraDevice。

打开:frameworks\base\core\java\android\hardware\camera2\CameraDevice.java

开篇来了一个枚举的Template:

@IntDef(prefix = {"TEMPLATE_"}, value =
    {TEMPLATE_PREVIEW,
     TEMPLATE_STILL_CAPTURE,
     TEMPLATE_RECORD,
     TEMPLATE_VIDEO_SNAPSHOT,
     TEMPLATE_ZERO_SHUTTER_LAG,
     TEMPLATE_MANUAL})

其中有几个挺熟悉的:

  • TEMPLATE_PREVIEW 是预览的
  • TEMPLATE_STILL_CAPTURE 是拍照的
  • TEMPLATE_RECORD 录像的

接下来看到几个熟悉的方法

1. 获取CameraId的

public abstract String getId();

2. 创建会话的

public abstract void createCaptureSession(@NonNull List<Surface> outputs, @NonNull CameraCaptureSession.StateCallback callback, @Nullable Handler handler)
public abstract void createCaptureSessionByOutputConfigurations(List<OutputConfiguration> outputConfigurations,CameraCaptureSession.StateCallback callback, @Nullable Handler handler)

public abstract void createCustomCaptureSession(
            InputConfiguration inputConfig,
            @NonNull List<OutputConfiguration> outputs,
            @SessionOperatingMode int operatingMode,
            @NonNull CameraCaptureSession.StateCallback callback,
            @Nullable Handler handler)
            
public void createCaptureSession(SessionConfiguration config)

前两种形式比较常见,第3种createCustomCaptureSession,没见过,不知道是什么,最后一个,主要是参数比较特殊,是SessionConfiguration类型

我们去看下SessionConfiguration 是什么
frameworks\base\core\java\android\hardware\camera2\params\SessionConfiguration.java

public final class SessionConfiguration implements Parcelable {
    private static final String TAG = "SessionConfiguration";
    
    // Camera capture session related parameters.
    private List<OutputConfiguration> mOutputConfigurations;
    private CameraCaptureSession.StateCallback mStateCallback;
    private int mSessionType;
    private Executor mExecutor = null;
    private InputConfiguration mInputConfig = null;
    private CaptureRequest mSessionParameters = null;

    /**
     * Create a new {@link SessionConfiguration}.
     *
     * @param sessionType The session type.
     * @param outputs A list of output configurations for the capture session.
     * @param executor The executor which should be used to invoke the callback. In general it is
     *                 recommended that camera operations are not done on the main (UI) thread.
     * @param cb A state callback interface implementation.
     *
     * @see #SESSION_REGULAR
     * @see #SESSION_HIGH_SPEED
     * @see CameraDevice#createCaptureSession(List, CameraCaptureSession.StateCallback, Handler)
     * @see CameraDevice#createCaptureSessionByOutputConfigurations
     * @see CameraDevice#createReprocessableCaptureSession
     * @see CameraDevice#createConstrainedHighSpeedCaptureSession
     */
    public SessionConfiguration(@SessionMode int sessionType,
            @NonNull List<OutputConfiguration> outputs,
            @NonNull @CallbackExecutor Executor executor,
            @NonNull CameraCaptureSession.StateCallback cb) {
        mSessionType = sessionType;
        mOutputConfigurations = Collections.unmodifiableList(new ArrayList<>(outputs));
        mStateCallback = cb;
        mExecutor = executor;
    }
  }

我截取了部分代码,可以看到SessionConfiguration 就是将原本一个个传给createCaptureSession的参数封装起来了,这样当然只传一个SessionConfiguration 对象就够了。

回到CameraDevice对象,继续往下看

3. 创建CaptureRequest的

public abstract CaptureRequest.Builder createCaptureRequest(@RequestTemplate int templateType)
public CaptureRequest.Builder createCaptureRequest(@RequestTemplate int templateType,Set<String> physicalCameraIdSet) 

这个也很好理解,根据传入的 templateType ,创建CaptureRequest.Builder,为后面的拍照请求做准备

4. 关闭Camera

public abstract void close();

5. 一个内部类,关于Camera发生错误的类型定义和Camera状态的回调

public static abstract class StateCallback {
	// error code的类型
	public static final int ERROR_CAMERA_IN_USE = 1;
	public static final int ERROR_MAX_CAMERAS_IN_USE = 2;
	public static final int ERROR_CAMERA_DISABLED = 3;
	public static final int ERROR_CAMERA_DEVICE = 4;
	public static final int ERROR_CAMERA_SERVICE = 5;
	
	public abstract void onOpened(@NonNull CameraDevice camera);
	
	public void onClosed(@NonNull CameraDevice camera) {
            // Default empty implementation
        }
	public abstract void onDisconnected(@NonNull CameraDevice camera);
	
	public abstract void onError(@NonNull CameraDevice camera,
                @ErrorCode int error);

}

CameraDevic的内容差不多就是这些了。我们注意到,CameraDevice是一个抽象类,它定义了一些方法,但是没有实现这些方法。

CameraDeviceImpl是CameraDevice的继承实现类
CameraDeviceImpl中有一个非常重要的成员变量

private ICameraDeviceUserWrapper mRemoteDevice;

你还记得这个成员变量的赋值来历吗? 我们在openCamera的流程中讲到了,在回顾下
在CameraManager中的openCameraDeviceUserAsync方法中,我们通过CameraService去连接Camera,得到一个cameraUser对象,

ICameraDeviceUser cameraUser = null;
// 创建CameraDeviceImpl
android.hardware.camera2.impl.CameraDeviceImpl deviceImpl =
                    new android.hardware.camera2.impl.CameraDeviceImpl(
                        cameraId,
                        callback,
                        executor,
                        characteristics,
                        mContext.getApplicationInfo().targetSdkVersion);
 //通过CameraService连接CameraId对应的ICameraDeviceUser 
cameraUser = cameraService.connectDevice(callbacks, cameraId,
                            mContext.getOpPackageName(), mContext.getAttributionTag(), uid);
//将得到ICameraDeviceUser 对象赋给CmaeraDeviceImpl                           
deviceImpl.setRemoteDevice(cameraUser);

CameraDeviceImpl后续的操作都离不开这个ICameraDeviceUser对象

回到CameraDeviceImpl类中,在CameraManager调用CameraDevideImpl的setRemoteDevice方法,CameraDevideImpl先是将ICameraDeviceUser 包装成ICameraDeviceUserWrapper对象,然后执行了两个Runnable

public void setRemoteDevice(ICameraDeviceUser remoteDevice) throws CameraAccessException {
        synchronized(mInterfaceLock) {
            // TODO: Move from decorator to direct binder-mediated exceptions
            // If setRemoteFailure already called, do nothing
            if (mInError) return;

            mRemoteDevice = new ICameraDeviceUserWrapper(remoteDevice);

            IBinder remoteDeviceBinder = remoteDevice.asBinder();
            // For legacy camera device, remoteDevice is in the same process, and
            // asBinder returns NULL.
            if (remoteDeviceBinder != null) {
                try {
                    remoteDeviceBinder.linkToDeath(this, /*flag*/ 0);
                } catch (RemoteException e) {
                    CameraDeviceImpl.this.mDeviceExecutor.execute(mCallOnDisconnected);

                    throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED,
                            "The camera device has encountered a serious error");
                }
            }

            mDeviceExecutor.execute(mCallOnOpened);
            mDeviceExecutor.execute(mCallOnUnconfigured);
        }
    }

private final Runnable mCallOnOpened = new Runnable() {
        @Override
        public void run() {
            StateCallbackKK sessionCallback = null;
            synchronized(mInterfaceLock) {
                if (mRemoteDevice == null) return; // Camera already closed

                sessionCallback = mSessionStateCallback;
            }
            if (sessionCallback != null) {
                sessionCallback.onOpened(CameraDeviceImpl.this);
            }
            mDeviceCallback.onOpened(CameraDeviceImpl.this);//我们在CameraApp中接收到 onOpened回调,并拿到CameraDeviceImpl对象
        }
    };

private final Runnable mCallOnUnconfigured = new Runnable() {
    @Override
    public void run() {
        StateCallbackKK sessionCallback = null;
        synchronized(mInterfaceLock) {
            if (mRemoteDevice == null) return; // Camera already closed

            sessionCallback = mSessionStateCallback;
        }
        if (sessionCallback != null) {
            sessionCallback.onUnconfigured(CameraDeviceImpl.this);
        }
    }
};

我们在来看下CameraDeviceImpl是如何实现createCaptureSession的,毕竟创建会话是很重要的流程

@Override
public void createCaptureSessionByOutputConfigurations(
        List<OutputConfiguration> outputConfigurations,
        CameraCaptureSession.StateCallback callback, Handler handler)
        throws CameraAccessException {

    // OutputConfiguration objects are immutable, but need to have our own array
    List<OutputConfiguration> currentOutputs = new ArrayList<>(outputConfigurations);

    createCaptureSessionInternal(null, currentOutputs, callback, checkAndWrapHandler(handler),
            /*operatingMode*/ICameraDeviceUser.NORMAL_MODE, /*sessionParams*/null);
}

流程转到 createCaptureSessionInternal方法中
1,检查当前CameraDeviceImpl的状态,如果当前Camera的状态异常,就直接抛出Exception退出,不在进行后续流程
2,如果 mCurrentSession != null ,说明当前CameraDevice已经创建过会话,需要将这个已经创建的会话close掉
3,通过mRemoteDevice对象,传入相关的 OutputConfiguration、CaptureRequest等参数去创建会话
4,mRemoteDevice创建会话成功,构建CameraCaptureSessionImpl对象

private void createCaptureSessionInternal(InputConfiguration inputConfig,
            List<OutputConfiguration> outputConfigurations,
            CameraCaptureSession.StateCallback callback, Executor executor,
            int operatingMode, CaptureRequest sessionParams) throws CameraAccessException {
        synchronized(mInterfaceLock) {
            if (DEBUG) {
                Log.d(TAG, "createCaptureSessionInternal");
            }
			// 1,检查当前CameraDeviceImpl的状态
            checkIfCameraClosedOrInError();

            boolean isConstrainedHighSpeed =
                    (operatingMode == ICameraDeviceUser.CONSTRAINED_HIGH_SPEED_MODE);
            if (isConstrainedHighSpeed && inputConfig != null) {
                throw new IllegalArgumentException("Constrained high speed session doesn't support"
                        + " input configuration yet.");
            }

            // Notify current session that it's going away, before starting camera operations
            // After this call completes, the session is not allowed to call into CameraDeviceImpl
            if (mCurrentSession != null) {//2,当前CameraDevice已经创建过会话,需要将这个已经创建的会话close掉
                mCurrentSession.replaceSessionClose();
            }

            // TODO: dont block for this
            boolean configureSuccess = true;
            CameraAccessException pendingException = null;
            Surface input = null;
            try {
                // configure streams and then block until IDLE
                configureSuccess = configureStreamsChecked(inputConfig, outputConfigurations,
                        operatingMode, sessionParams);//3,创建会话
                if (configureSuccess == true && inputConfig != null) {
                    input = mRemoteDevice.getInputSurface();
                }
            } catch (CameraAccessException e) {
                configureSuccess = false;
                pendingException = e;
                input = null;
                if (DEBUG) {
                    Log.v(TAG, "createCaptureSession - failed with exception ", e);
                }
            }

            // Fire onConfigured if configureOutputs succeeded, fire onConfigureFailed otherwise.
            CameraCaptureSessionCore newSession = null;
            if (isConstrainedHighSpeed) {
                ArrayList<Surface> surfaces = new ArrayList<>(outputConfigurations.size());
                for (OutputConfiguration outConfig : outputConfigurations) {
                    surfaces.add(outConfig.getSurface());
                }
                StreamConfigurationMap config =
                    getCharacteristics().get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
                SurfaceUtils.checkConstrainedHighSpeedSurfaces(surfaces, /*fpsRange*/null, config);

                newSession = new CameraConstrainedHighSpeedCaptureSessionImpl(mNextSessionId++,
                        callback, executor, this, mDeviceExecutor, configureSuccess,
                        mCharacteristics);
            } else {//4,mRemoteDevice创建会话成功,构建CameraCaptureSessionImpl对象
                newSession = new CameraCaptureSessionImpl(mNextSessionId++, input,
                        callback, executor, this, mDeviceExecutor, configureSuccess);
            }

            // TODO: wait until current session closes, then create the new session
            mCurrentSession = newSession;

            if (pendingException != null) {
                throw pendingException;
            }

            mSessionStateCallback = mCurrentSession.getDeviceStateCallback();
        }
    }

这个流程中第3点: 调用 configureStreamsChecked 去向底层配置surface ,我们来详细看下
①,检查当前CameraDevice的状态
②,for循环
如果是通过createCaptureSessionByOutputConfigurations第一次调过来,当前CameraDeviceImpl的mConfiguredOutputs成员变量为null,不走for循环
③:stopRepeating() 创建会话时会停止当前已经进行的预览
④,mRemoteDevice.beginConfigure();
⑤:将传入的 List outputs 一个个循环设置到mRemoteDevice.createStrea
⑥:将传入的OutputConfiguration对象put到当前CameraDeviceImpl的mConfiguredOutputs集合中管理
⑦:mRemoteDevice.endConfigure(operatingMode, null);

public boolean configureStreamsChecked(InputConfiguration inputConfig,
            List<OutputConfiguration> outputs, int operatingMode, CaptureRequest sessionParams)
                    throws CameraAccessException {
        // Treat a null input the same an empty list
        if (outputs == null) {
            outputs = new ArrayList<OutputConfiguration>();
        }
        if (outputs.size() == 0 && inputConfig != null) {
            throw new IllegalArgumentException("cannot configure an input stream without " +
                    "any output streams");
        }

        checkInputConfiguration(inputConfig);

        boolean success = false;

        synchronized(mInterfaceLock) {
            checkIfCameraClosedOrInError();//1,检查当前CameraDevice的状态
            // Streams to create
            HashSet<OutputConfiguration> addSet = new HashSet<OutputConfiguration>(outputs);
            // Streams to delete
            List<Integer> deleteList = new ArrayList<Integer>();

            // Determine which streams need to be created, which to be deleted
            for (int i = 0; i < mConfiguredOutputs.size(); ++i) {//2 for循环
                int streamId = mConfiguredOutputs.keyAt(i);
                OutputConfiguration outConfig = mConfiguredOutputs.valueAt(i);

                if (!outputs.contains(outConfig) || outConfig.isDeferredConfiguration()) {
                    // Always delete the deferred output configuration when the session
                    // is created, as the deferred output configuration doesn't have unique surface
                    // related identifies.
                    deleteList.add(streamId);
                } else {
                    addSet.remove(outConfig);  // Don't create a stream previously created
                }
            }

            mDeviceExecutor.execute(mCallOnBusy);
            stopRepeating();//3,创建会话时会停止当前已经进行的预览

            try {
                waitUntilIdle();

                mRemoteDevice.beginConfigure();//4,

                // reconfigure the input stream if the input configuration is different.
                InputConfiguration currentInputConfig = mConfiguredInput.getValue();
                if (inputConfig != currentInputConfig &&
                        (inputConfig == null || !inputConfig.equals(currentInputConfig))) {
                    if (currentInputConfig != null) {
                        mRemoteDevice.deleteStream(mConfiguredInput.getKey());
                        mConfiguredInput = new SimpleEntry<Integer, InputConfiguration>(
                                REQUEST_ID_NONE, null);
                    }
                    if (inputConfig != null) {
                        int streamId = mRemoteDevice.createInputStream(inputConfig.getWidth(),
                                inputConfig.getHeight(), inputConfig.getFormat());
                        mConfiguredInput = new SimpleEntry<Integer, InputConfiguration>(
                                streamId, inputConfig);
                    }
                }

                // Delete all streams first (to free up HW resources)
                for (Integer streamId : deleteList) {
                    mRemoteDevice.deleteStream(streamId);
                    mConfiguredOutputs.delete(streamId);
                }

                // Add all new streams
                for (OutputConfiguration outConfig : outputs) {
                    if (addSet.contains(outConfig)) {
                        int streamId = mRemoteDevice.createStream(outConfig);//5,将传入的 List<OutputConfiguration> outputs 一个个循环设置到mRemoteDevice
                        mConfiguredOutputs.put(streamId, outConfig);//6,将传入的OutputConfiguration对象put到当前CameraDeviceImpl的mConfiguredOutputs集合中管理
                    }
                }

                int offlineStreamIds[];
                if (sessionParams != null) {
                    offlineStreamIds = mRemoteDevice.endConfigure(operatingMode,
                            sessionParams.getNativeCopy());
                } else {
                    offlineStreamIds = mRemoteDevice.endConfigure(operatingMode, null);//7,
                }

                mOfflineSupport.clear();
                if ((offlineStreamIds != null) && (offlineStreamIds.length > 0)) {
                    for (int offlineStreamId : offlineStreamIds) {
                        mOfflineSupport.add(offlineStreamId);
                    }
                }

                success = true;
            } catch (IllegalArgumentException e) {
                // OK. camera service can reject stream config if it's not supported by HAL
                // This is only the result of a programmer misusing the camera2 api.
                Log.w(TAG, "Stream configuration failed due to: " + e.getMessage());
                return false;
            } catch (CameraAccessException e) {
                if (e.getReason() == CameraAccessException.CAMERA_IN_USE) {
                    throw new IllegalStateException("The camera is currently busy." +
                            " You must wait until the previous operation completes.", e);
                }
                throw e;
            } finally {
                if (success && outputs.size() > 0) {
                    mDeviceExecutor.execute(mCallOnIdle);
                } else {
                    // Always return to the 'unconfigured' state if we didn't hit a fatal error
                    mDeviceExecutor.execute(mCallOnUnconfigured);
                }
            }
        }

        return success;
    }

CameraCaptureSessionImpl 也是很重要的一块内容,我们会在下篇文章中单独来讲。

我们在继续看CameraDeviceImpl中的其它内容

创建CaptureRequest对象 这里的关键逻辑是:mRemoteDevice.createDefaultRequest(templateType);

@Override
public CaptureRequest.Builder createCaptureRequest(int templateType)
        throws CameraAccessException {
    synchronized(mInterfaceLock) {
        checkIfCameraClosedOrInError();

        CameraMetadataNative templatedRequest = null;

        templatedRequest = mRemoteDevice.createDefaultRequest(templateType);

        // If app target SDK is older than O, or it's not a still capture template, enableZsl
        // must be false in the default request.
        if (mAppTargetSdkVersion < Build.VERSION_CODES.O ||
                templateType != TEMPLATE_STILL_CAPTURE) {
            overrideEnableZsl(templatedRequest, false);
        }

        CaptureRequest.Builder builder = new CaptureRequest.Builder(
                templatedRequest, /*reprocess*/false, CameraCaptureSession.SESSION_ID_NONE,
                getId(), /*physicalCameraIdSet*/ null);

        return builder;
    }
}

prepare方法:mRemoteDevice.prepare(streamId);

public void prepare(Surface surface) throws CameraAccessException {
        if (surface == null) throw new IllegalArgumentException("Surface is null");

        synchronized(mInterfaceLock) {
            checkIfCameraClosedOrInError();
            int streamId = -1;
            for (int i = 0; i < mConfiguredOutputs.size(); i++) {
                final List<Surface> surfaces = mConfiguredOutputs.valueAt(i).getSurfaces();
                if (surfaces.contains(surface)) {
                    streamId = mConfiguredOutputs.keyAt(i);
                    break;
                }
            }
            if (streamId == -1) {
                throw new IllegalArgumentException("Surface is not part of this session");
            }

            mRemoteDevice.prepare(streamId);
        }
    }

updateOutputConfiguration:mRemoteDevice.updateOutputConfiguration(streamId, config);

public void updateOutputConfiguration(OutputConfiguration config)
            throws CameraAccessException {
        synchronized(mInterfaceLock) {
            checkIfCameraClosedOrInError();
            int streamId = -1;
            for (int i = 0; i < mConfiguredOutputs.size(); i++) {
                if (config.getSurface() == mConfiguredOutputs.valueAt(i).getSurface()) {
                    streamId = mConfiguredOutputs.keyAt(i);
                    break;
                }
            }
            if (streamId == -1) {
                throw new IllegalArgumentException("Invalid output configuration");
            }

            mRemoteDevice.updateOutputConfiguration(streamId, config);
            mConfiguredOutputs.put(streamId, config);
        }
    }

tearDown:mRemoteDevice.tearDown(streamId);

public void tearDown(Surface surface) throws CameraAccessException {
        if (surface == null) throw new IllegalArgumentException("Surface is null");

        synchronized(mInterfaceLock) {
            checkIfCameraClosedOrInError();
            int streamId = -1;
            for (int i = 0; i < mConfiguredOutputs.size(); i++) {
                if (surface == mConfiguredOutputs.valueAt(i).getSurface()) {
                    streamId = mConfiguredOutputs.keyAt(i);
                    break;
                }
            }
            if (streamId == -1) {
                throw new IllegalArgumentException("Surface is not part of this session");
            }

            mRemoteDevice.tearDown(streamId);
        }
    }

finalizeOutputConfigs:mRemoteDevice.finalizeOutputConfigurations(streamId, config);

public void finalizeOutputConfigs(List<OutputConfiguration> outputConfigs)
            throws CameraAccessException {
        if (outputConfigs == null || outputConfigs.size() == 0) {
            throw new IllegalArgumentException("deferred config is null or empty");
        }

        synchronized(mInterfaceLock) {
            checkIfCameraClosedOrInError();

            for (OutputConfiguration config : outputConfigs) {
                int streamId = -1;
                for (int i = 0; i < mConfiguredOutputs.size(); i++) {
                    // Have to use equal here, as createCaptureSessionByOutputConfigurations() and
                    // createReprocessableCaptureSessionByConfigurations() do a copy of the configs.
                    if (config.equals(mConfiguredOutputs.valueAt(i))) {
                        streamId = mConfiguredOutputs.keyAt(i);
                        break;
                    }
                }
                if (streamId == -1) {
                    throw new IllegalArgumentException("Deferred config is not part of this "
                            + "session");
                }

                if (config.getSurfaces().size() == 0) {
                    throw new IllegalArgumentException("The final config for stream " + streamId
                            + " must have at least 1 surface");
                }
                mRemoteDevice.finalizeOutputConfigurations(streamId, config);
                mConfiguredOutputs.put(streamId, config);
            }
        }
    }

最后我们来看一个最最重要的方法:capture

public int capture(CaptureRequest request, CaptureCallback callback, Executor executor)
    throws CameraAccessException {
    if (DEBUG) {
        Log.d(TAG, "calling capture");
    }
    List<CaptureRequest> requestList = new ArrayList<CaptureRequest>();
    requestList.add(request);
    return submitCaptureRequest(requestList, callback, executor, /*streaming*/false);
}

哎呦喂,乍一看,啥都没干,就是submitCaptureRequest,看这方法的含义,只是提交一个拍照request

private int submitCaptureRequest(List<CaptureRequest> requestList, CaptureCallback callback,
            Executor executor, boolean repeating) throws CameraAccessException {
	//1,停止预览
	if (repeating) {
    	stopRepeating();
     }
	
	SubmitInfo requestInfo;
	//2,convertSurfaceToStreamId
   CaptureRequest[] requestArray = requestList.toArray(new CaptureRequest[requestList.size()]);
   // Convert Surface to streamIdx and surfaceIdx
   for (CaptureRequest request : requestArray) {
       request.convertSurfaceToStreamId(mConfiguredOutputs);
   }
	
	//3,mRemoteDevice 继续向底层提交request
	requestInfo = mRemoteDevice.submitRequestList(requestArray, repeating);
	
	//4,recoverStreamIdToSurface
	for (CaptureRequest request : requestArray) {
    	request.recoverStreamIdToSurface();
    }


其中第3,mRemoteDevice 继续向底层提交request,这里涉及到aidl的跨进程通信了,具体实现如下涉及两个类:
frameworks\hardware\interfaces\cameraservice\device\2.0\ICameraDeviceUser.hal下的
submitRequestList(vec requestList, bool isRepeating)
generates (Status status, SubmitInfo submitInfo);

frameworks\av\services\camera\libcameraservice\api2\CameraDeviceClient.cpp
binder::Status CameraDeviceClient::submitRequestList(
const std::vectorhardware::camera2::CaptureRequest& requests,
bool streaming,
/out/
hardware::camera2::utils::SubmitInfo *submitInfo)
这块的调用及底层逻辑我们后续在另起文章介绍。

CameraDeviceImpl的内容大致就是上面这些了,我们看到CameraDeviceImpl的大部分逻辑是通过其内部成员变量mRemoteDevice去继续向底层请求实现的。

Camera是一套完整的从面向用户的CameraApp到我们今天这篇文章分析的hardware.camera2,在到hal的体系,其中的每个环节都至关重要,而camera的学习便是打通这个流程的过程。

 类似资料: