Android Camera 之预览和拍照

前言:

        前面有介绍了CreateCaptureSession,当这一步成功完成后,就可以开始启动预览、拍照的流程了,即进入了Request阶段。跟前面介绍openCamera和CreateCaptureSession一样,我们也从上到下介绍这个Request 阶段,即从APP、native framework、HAL这三部分来介绍。

一、APP部分

       APP会通过下表中的方法开启预览或者拍照。Capture对应就是拍照相关的场景,Repeating对应预览、录像这些场景。其中Capture会优先于Repeating。

Type方法说明
RepeatingsetRepeatingRequest()CameraService会向底层不停重复地发这一个Request
setSingleRepeatingRequest()功能与setRepeatingRequest()一样,只不过setRepeatingRequest()用的是handler指定回调线程,setSingleRepeatingRequest()用的是executor去指定的
setRepeatingBurst()CameraService会向底层不停重复地发一组Request,常用于AOSP Slow Motion
Capturecapture()CameraService只会向底层发送一个Request,并且只发一次
captureSingleRequest()功能与capture()一样,只不过capture()用的是handler指定回调线程,captureSingleRequest()用的是executor去指定的
captureBurstRequests()向底层发送一组Request(这组Request是连续的),并且只发送一次。比如:连拍

       无论是调用上面哪种方法,函数里的参数里主要有两个:一个是CaptureRequest,里面包含有meta信息和Surafces,Surfaces必须是createCaptureSession时配置的那些surface的子集,CaptureRequest可以理解为让Camera底层知道该如何去处理这一帧数据;另外一个就是CallBack回调类,这个类里面有好几个方法,需要App去实作,以便于当Request处理完毕或者底层在处理Request时发生了异常时,能在对应的回调函数里做出对应的响应,比如onCaptureStarted()、onCaptureProgressed() onCaptureCompleted()、onCaptureFailed()等方法。

       无论是调用上面哪种方法,所带的CaptureRequest最终都会在CameraDeviceImpl里被统一包装成List<CaptureRequest>,再统一通过submitCaptureRequest()继续往下传。其中submitCaptureRequest这个函数参数里有repeating这个变量来标识当前这个是一个(组)Repeating request还是Capture Request,传递的flow如下:

       首先判断这次发送的Request是否是Repeating类型,如果是,则先stopRepeating(),这其实是将前一次发送的RepeatingRequest清除掉

1、convertSurfaceToStreamId():

       在config阶段,已经将每路stream的streamId和它对应的相关配置信息存储在了mConfigureOutputs里。这里是从mConfigureOutputs里找到这单个request所带下来的所有surface对应的streamId与surfaceId,其中surfaceId从0递增。有带下来多少个request这里就会调用多少次

2、mRemoteDevice.submitRequestList()

       这里调用的是CameraDeviceClient::submitRequestList(),这一步已经是在呼叫native framework层了,里面做的事情在后面native framework部分来分析。这里需要说明的是,无论是capture request还是Repeating request都是通过这个接口送到native framework的,只不过接口中的参数里有一个叫repeating的变量,它标记了这个request的类型,所以看起来这两种类型的request虽然都是通过相同的接口送下去的,但是底层对它们的行为可能还是会有差异的。

二、Native Framework

1、CameraDeviceClient::submitRequestList()

         这里面要先做一些合法性检查,以及筛选和更新一些meta的信息。 比如:

  • input stream没有配置时、Repeating Request、multicam这三种情况不能有reprocess;
  • request带下来的surface target不能为空
  • 检查这一组Request里,每一个Request所带下来的PhysicalCameraSetting中的每一个key是否都有在SupportedPhysicalRequestKey当中,会把不包含范围内的Key过滤掉。

2、setStreamingRequestList() & captureList()

       如果这个Request是Repeating类型的,就会调用Camera3Device::setStreamingRequestList();否则就调用Camera3Device::captureList();前面的时序图中红框中的flow是Repeating request走的流程,绿框中的是Capture request走的流程。从时序图中对比可以看出,它们之间的flow其实大同小异,接下来就看看这两条flow里一些共同的东西。这里需要点一下这两条flow最后的结果:capture request会被放在mRequestQueue里;Repeating  Request会放在mRepeatingRequests,无论是看code还是上面的时序图都可以看到这个结果。

   2.1 convertMetadataListToRequestListLocked()

         从这个方法的名字可以看出来,是要将meta资讯转换为Request。根据前面每个Request里的meta,创建List<sp<CaptureRequest>>,其中这里的CaptureRequest是定义在Camera3Device::HalInterface::CaptureRequest,其类图大致如下:

status_t Camera3Device::convertMetadataListToRequestListLocked(
        const List<const PhysicalCameraSettingsList> &metadataList,
        const std::list<const SurfaceMap> &surfaceMaps,
        bool repeating, nsecs_t requestTimeNs,
        RequestList *requestList) {
    if (requestList == NULL) {
        CLOGE("requestList cannot be NULL.");
        return BAD_VALUE;
    }

    int32_t burstId = 0;
    List<const PhysicalCameraSettingsList>::const_iterator metadataIt = metadataList.begin();
    std::list<const SurfaceMap>::const_iterator surfaceMapIt = surfaceMaps.begin();
    for (; metadataIt != metadataList.end() && surfaceMapIt != surfaceMaps.end();
            ++metadataIt, ++surfaceMapIt) {
        sp<CaptureRequest> newRequest = setUpRequestLocked(*metadataIt, *surfaceMapIt);
        if (newRequest == 0) {
            CLOGE("Can't create capture request");
            return BAD_VALUE;
        }

        newRequest->mRepeating = repeating;
        newRequest->mRequestTimeNs = requestTimeNs;

        // Setup burst Id and request Id
        newRequest->mResultExtras.burstId = burstId++;
        auto requestIdEntry = metadataIt->begin()->metadata.find(ANDROID_REQUEST_ID);
        if (requestIdEntry.count == 0) {
            CLOGE("RequestID does not exist in metadata");
            return BAD_VALUE;
        }
        newRequest->mResultExtras.requestId = requestIdEntry.data.i32[0];

        requestList->push_back(newRequest);

        ALOGV("%s: requestId = %" PRId32, __FUNCTION__, newRequest->mResultExtras.requestId);
    }
    if (metadataIt != metadataList.end() || surfaceMapIt != surfaceMaps.end()) {
        ALOGE("%s: metadataList and surfaceMaps are not the same size!", __FUNCTION__);
        return BAD_VALUE;
    }

    // Setup batch size if this is a high speed video recording request.
    if (mIsConstrainedHighSpeedConfiguration && requestList->size() > 0) {
        auto firstRequest = requestList->begin();
        for (auto& outputStream : (*firstRequest)->mOutputStreams) {
            if (outputStream->isVideoStream()) {
                (*firstRequest)->mBatchSize = requestList->size();
                outputStream->setBatchSize(requestList->size());
                break;
            }
        }
    }

    return OK;
}

       由这个函数可以看出,其实就是将这些Meta资讯包装成CaptureRequest,这些Request就像前面说的Repeating  Request会放在mRepeatingRequests;capture request会被放在mRequestQueue里。

3、RequestThread::threadLoop()

      前面有说到:capture request会被放在mRequestQueue里;Repeating  Request会放在mRepeatingRequests。threadLoop这个线程主要就是在向Hal层送Request,接下来看看其大致的流程

   3.1waitForNextRequestBatch():

       要想往底层送Request,首先就得有Request;waitForNextRequestBatch这个方法里所做的事情就是在获取Request,而这个方法又主要是通过waitForNextRequestLocked()完成的,waitForNextRequestLocked这个方法每次能拿到一个Request,如果说是有N(batch size)个Request,那就会call N次waitForNextRequestLocked()去拿到这些Request。

      waitForNextRequestLocked():优先从mRequestQueue里去取Request,每从队头拿到一个Request后,就会把这个Request从队头删除;当mRequestQueue里没有request时,则会先从mRepeatingRequests里拿到第一个Request,再把mRepeatingRequests里剩余的Request复制到mRequestQueue里面(这样后面就可以从mRequestQueue里直接拿Request了);这个函数每呼叫一次就会拿到一个Request,如果bach size>1的话(比如Consrained High Speed模式,即慢动作模式,App会调用setRepeatingBurst()同时下发多个CaptureRequest),则会呼叫多次,每拿到一次Request,这个Request其对应的frameNo会自动加1。deque出来的每一个Request又会被包装成NextRequest,它的数据结构如下:          

       在开头有说过capture要优先于Repeating,这个是怎么体现出来的呢?答案就在这个waitForNextRequestLocked()函数里:RequestThread会优先从mRequestQueue里去拿Request,而Capture的Request就是直接放在mRequestQueue里的;只有在mRequestQueue里空的时候,才会去从mRepeatingRequests里面去取Request,把Request复制到mRequestQueue里。最后从mRequestQueue里面将Request一个一个地push到RequestThread::mNextRequests里面。

 3.2 updateSessionParameters()

          这里是为了检查这次的Request所带的Session param相对上一次Request的Session params是否有发生变化。因为如果有发生变化的话,可能会造成reconfig;如果没有发生变化的话,则肯定不用reconfig,直接返回false。

           如果当前的Request所携带的Session params相对于前面的Request来说,有发生变化的话,需要交由HAL层去决策有没有必要reconfig一次,其中呼叫HAL层的接口为isReconfigurationRequired(),这个函数会把前一次的Session params和当前这次的Session params送到HAL层,让HAL层自己决定。这个接口定义在:

 /hardware/interfaces/camera/devices/3.5/iCameraDeviceSession.hal

 3.3 prepareHalRequests()

  • NextRequest::outputBuffers=insert(camera_stream_buffer_t,0,captureRequest->mOutputStreams.size())

         NextRequest::outputBuffers是一个保存stream buffer的容器, 这里就是要在outputBuffers的容器中序号0的后面插入captureRequest->mOutputStreams.size()个camera_stream_buffer对象,即这个Request有需要Hal层输出几块Buffer,这里就有多少个camera_stream_buffer。

  • 根据是否支持UseHalBufferManager决定是否要去getBuffer。

        如果不支持UseHalBufferManager,则需为每一个outputStream获取Buffer,从BufferQueue当中去deque GraphicBuffer。关于UseHalBufManager:如果没有开启UseHalBufManager,则Famework下request的时候,streamBuffer会包含buffer handle和Buffer Id;如果有开启UseHalBufferManager,FrameWork还是会带StreamBuffer,但是StreamBuffer不会包含buffer handle和Buffer Id,HAL要通过requestStreamBuffers()这个API去向FrameWork请求buffer,目的是在要真正使用buffer的时候,才去拿这块Buffer,以达到节省memory的效果。它是在openCamera()时,在camera3Device::initialize()时,根据ANDROID_INFO_SUPPORT_MANAGEMENT_VERSION来决定的,只有这个TAG的值为:ANDROID_INFO_SUPPORT_MANAGEMENT_VERSION_HIDL_DEVICE_3_X时才会将mUseHalBufManager置为true。

  • registerInflight()

       将这个Request的信息封装成InFlightRequest,并注册进mInFlightMap,它里面的Requests是表示当前Hal层还未处理或者未处理完的Request,关于InFlightRequest这个类的信息可以看下面这个类图。      

3.4 sendRequestsBatch()

  • wrapAsHidlRequest(),

       将每个Request都包装成Hal规范的Request,Hal的Request大致的数据结构图如下图所示:   

      

      这个函数里会构造Hal request的frameNumber,每块Buffer的Buffer handle、Buffer Id、Buffer streamId等资讯。关于Stream Id就不过多解释了,接下来看看这个Buffer Handle与Buffer Id。

  • Buffer Handle:即buffer_handle_t,里面有包含这个Buffer的内存地址。
  • Buffer Id:每一块Buffer都有自己的Id,Camera3Device的mBufferRecords这个成员变量如下图所示,其有一个mBufferIdMaps的变量,它就记录了每个Stream中某块Buffer的Id,也就是说根据StreamId和Buffer地址,可以确定一个Id值。因为BufferQueue中的Buffer是在不断轮转的,所以这个Id值也会是不断地在重复。

    

  • processCaptureRequest()

       这个函数,即mHidlSession->processCaptureRequest(),就是将前面的一个(组)Hal request送进Hal层,然后就等待Hal层处理了。这个接口定义在:

   /hardware/interfaces/camera/devices/3.X/iCameraDeviceSession.hal

三 Hal层

         native framrwork中用processCaptureRequest()这个接口,通过Hidl将Request送进了Hal层;那Hal层拿到这个Request,就会根据这个Request里所带的资讯开始处理了,同样还是因为每个厂商在这一层的实作有差异,就不详细介绍了。

      Hal层处理完Request后,会通过processCaptureResult()将处理结果返回给上层,这里在后面单独拿一篇简单介绍一下。

总结:

         整个Requets过程,就是APP调用一次下request的接口,不管是Capture request,还是Repeating request,最后都会被push进RequestQueue里面。只不过Capture Request是直接push进mRequestQueue里的;而Repeating Request是先放到mRepeatingRequests里,再push进mRequestQueue,这就实现处理request时,Capture request比Repeating request优先级更高。RequestThread不断地去将mRepeatingRequests里的所有request复制出一份,不断地push到mRequestQueue里,如此循环,以达到不断地向底层重复下Request的目的。

          framework在向Hal输送request之前,需要对request进行转换和包装,使之成为Hal规范的Request,这个Hal Request会包含有这个request的frameNumber、metadata资讯,如果是没有支持UseHalBufferManager,Hal Request还会带Buffer Handle和Buffer Id到Hal层;如果有支持UseHalBufferManager,就不会带Buffer Handle和Buffer Id给Hal层,而是等Hal层需要使用到这些Buffer时,再通过requestStreamBuffers()这个API去向FrameWork请求buffer,这样可以达到节省memory的效果。把Request送给Hal之后,就等待Hal的处理结果,最后Hal会通过ProcessCaptureResult()将处理结果返回。

      关于Repeating,APP每新发送一次Repeating Requests,FW会先stopRepeating(),即把上一次mRepeatingRequests里的Requests清除掉,然后把新下的Repeating Requests放进去,然后开始新的Repeating循环。

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
Camerax是Android相机框架的一部分,它提供了便捷的API来实现相机预览拍照功能。 要使用Camerax进行预览拍照,我们首先需要配置相机权限。在AndroidManifest.xml文件中,添加以下权限: ``` <uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> ``` 接下来,在activity的布局文件中添加一个预览用的TextureView: ``` <TextureView android:id="@+id/previewView" android:layout_width="match_parent" android:layout_height="match_parent" /> ``` 在相关的Activity中,我们使用Camerax来实现预览拍照功能。首先,在onCreate方法中初始化Camerax的实例: ``` PreviewConfig previewConfig = new PreviewConfig.Builder().build(); Preview preview = new Preview(previewConfig); ImageCaptureConfig captureConfig = new ImageCaptureConfig.Builder().build(); ImageCapture imageCapture = new ImageCapture(captureConfig); CameraX.bindToLifecycle(this, preview, imageCapture); ``` 然后,我们需要将预览显示在TextureView中: ``` TextureView previewView = findViewById(R.id.previewView); preview.setSurfaceProvider(previewView.getSurfaceProvider()); ``` 最后,我们可以在需要的时候进行拍照: ``` // 通过点击按钮触发拍照 Button captureButton = findViewById(R.id.captureButton); captureButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { File file = new File(getExternalFilesDir(Environment.DIRECTORY_PICTURES), "IMG_" + System.currentTimeMillis() + ".jpg"); ImageCapture.OutputFileOptions outputFileOptions = new ImageCapture.OutputFileOptions.Builder(file).build(); imageCapture.takePicture(outputFileOptions, ContextCompat.getMainExecutor(MainActivity.this), new ImageCapture.OnImageSavedCallback() { @Override public void onImageSaved(@NonNull ImageCapture.OutputFileResults outputFileResults) { // 在此处对拍照后的图片进行处理 } @Override public void onError(@NonNull ImageCaptureException exception) { // 拍照出现错误时的处理 } }); } }); ``` 通过以上步骤,我们就可以使用Camerax来实现Android相机的预览拍照功能。它提供了简单而强大的API,使得实现相机应用变得更加容易。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值