Android Camera原理之createCaptureSession模块

《Android Camera架构》
《Android Camera进程间通信类总结》
《Android Camera模块解析之拍照》
《Android Camera模块解析之视频录制》
《Android Camera原理之CameraDeviceCallbacks回调模块》
《Android Camera原理之openCamera模块(一)》
《Android Camera原理之openCamera模块(二)》
《Android Camera原理之createCaptureSession模块》
《Android Camera原理之setRepeatingRequest与capture模块》
《Android Camera原理之编译》
《Android Camera原理之camera provider启动》
《Android Camera原理之cameraserver与cameraprovider是怎样联系的》
《Android Camera原理之camera service与camera provider session会话与capture request轮转》
《Android Camera原理之camera HAL底层数据结构与类总结》
《Android Camera原理之camera service类与接口关系》

Camera操作过程中最重要的四个步骤:

  • CameraManager-->openCamera ---> 打开相机
  • CameraDeviceImpl-->createCaptureSession ---> 创建捕获会话
  • CameraCaptureSession-->setRepeatingRequest ---> 设置预览界面
  • CameraDeviceImpl-->capture ---> 开始捕获图片

之前我们介绍过openCamera模块:
《Android Camera原理之openCamera模块(一)》
《Android Camera原理之openCamera模块(二)》
其中讲到了Camera打开过程中很多类,Java层的,native层的,捋一捋这些类关系,进一步分析openCamera成功之后执行的CameraDeviceImpl-->createCaptureSession,openCamera执行成功的回调CameraDevice.StateCallback的onOpened(CameraDevice cameraDevice)方法,当前这个CameraDevice参数就是当前已经打开的相机设备。
获取了相机设备,接下来要创建捕捉会话,会话建立成功,可以在当前会话的基础上设置相机预览界面,这时候我们调整摄像头,就能看到屏幕上渲染的相机输入流了,接下来我们可以操作拍照片、拍视频等操作。


createCaptureSession流程.jpg

上面是createCaptureSession执行流程,涉及到的代码模块流程非常复杂,这儿只是提供了核心的一些流程。接下来我们会从代码结构和代码功能的基础上讲解这一块的内容。


1.CameraDeviceImpl->createCaptureSession

    public void createCaptureSession(List<Surface> outputs,
            CameraCaptureSession.StateCallback callback, Handler handler)
            throws CameraAccessException {
        List<OutputConfiguration> outConfigurations = new ArrayList<>(outputs.size());
        for (Surface surface : outputs) {
            outConfigurations.add(new OutputConfiguration(surface));
        }
        createCaptureSessionInternal(null, outConfigurations, callback,
                checkAndWrapHandler(handler), /*operatingMode*/ICameraDeviceUser.NORMAL_MODE,
                /*sessionParams*/ null);
    }

将Surface转化为OutputConfiguration,OutputConfiguration是一个描述camera输出数据的类,其中包括Surface和捕获camera会话的特定设置。从其类的定义来看,它是一个实现Parcelable的类,说明其必定要跨进程传输。

  • CameraDeviceImpl->createCaptureSession传入的Surface列表有几个?
    这儿的一个Surface表示输出流,Surface表示有多个输出流,我们有几个显示载体,就需要几个输出流。
    对于拍照而言,有两个输出流:一个用于预览、一个用于拍照。
    对于录制视频而言,有两个输出流:一个用于预览、一个用于录制视频。

参考源码最好理解了,下面是拍照的时候执行的代码:第一个surface是用于预览的,第二个surface,由于是拍照,所以使用ImageReader对象来获取捕获的图片,ImageReader在构造函数的时候调用nativeGetSurface获取Surface,这个Surface作为拍照的Surface来使用。

            mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()),
                    new CameraCaptureSession.StateCallback() {

                        @Override
                        public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {ss
                        }

                        @Override
                        public void onConfigureFailed(
                                @NonNull CameraCaptureSession cameraCaptureSession) {
                        }
                    }, null
            );

视频录制的也是一样的道理,视频录制使用MediaRecorder来获取视频信息,MediaRecorder在构造的时候也调用nativeGetSurface获取Surface。

ImageReader->OnImageAvailableListener回调

ImageR饿啊的人可以读取Surface对象的图片数据,将其转化为本地可以识别的数据,图片的长宽、时间信息等等。这些image数据信息的采集后续会详细说明,这儿先一笔带过。

    private class SurfaceImage extends android.media.Image {
        public SurfaceImage(int format) {
            mFormat = format;
        }
        @Override
        public void close() {
            ImageReader.this.releaseImage(this);
        }
        public ImageReader getReader() {
            return ImageReader.this;
        }
        private class SurfacePlane extends android.media.Image.Plane {
            private SurfacePlane(int rowStride, int pixelStride, ByteBuffer buffer) {
//......
            }
            final private int mPixelStride;
            final private int mRowStride;
            private ByteBuffer mBuffer;
        }
        private long mNativeBuffer;
        private long mTimestamp;
        private int mTransform;
        private int mScalingMode;
        private SurfacePlane[] mPlanes;
        private int mFormat = ImageFormat.UNKNOWN;
        // If this image is detached from the ImageReader.
        private AtomicBoolean mIsDetached = new AtomicBoolean(false);
        private synchronized native SurfacePlane[] nativeCreatePlanes(int numPlanes,
                int readerFormat);
        private synchronized native int nativeGetWidth();
        private synchronized native int nativeGetHeight();
        private synchronized native int nativeGetFormat(int readerFormat);
        private synchronized native HardwareBuffer nativeGetHardwareBuffer();
    }

在准备拍照之前,还会设置一下ImageReader的OnImageAvailableListener回调接口,调用setOnImageAvailableListener(OnImageAvailableListener listener, Handler handler)设置当前的OnImageAvailableListener 对象。这儿回调接口只有一个onImageAvailable函数,表示当前的捕捉的image已经可用了。然后我们在onImageAvailable回调函数中操作当前捕获的图片。

    public interface OnImageAvailableListener {
        void onImageAvailable(ImageReader reader);
    }

2.CameraDeviceImpl->createCaptureSessionInternal

    private void createCaptureSessionInternal(InputConfiguration inputConfig,
            List<OutputConfiguration> outputConfigurations,
            CameraCaptureSession.StateCallback callback, Executor executor,
            int operatingMode, CaptureRequest sessionParams)

传入的几个参数中,inputConfig为null,我们只需关注outputConfigurations即可。
createCaptureSessionInternal函数中代码很多,但是重要的就是执行配置Surface

                configureSuccess = configureStreamsChecked(inputConfig, outputConfigurations,
                        operatingMode, sessionParams);
                if (configureSuccess == true && inputConfig != null) {
                    input = mRemoteDevice.getInputSurface();
                }

如果配置surface成功,返回一个Input surface这个input surface是用户本地设置的一个输入流。接下来这个input对象会在构造CameraCaptureSessionImpl对象时被传入。 具体参考4.CameraCaptureSessionImpl构造函数

3.CameraDeviceImpl->configureStreamsChecked

下面这张图详细列出配置输入输出流函数中执行的主要步骤,由于当前的inputConfig为null,所以核心的执行就是下面粉红色框中的过程——创建输出流

configureStreamsChecked流程对比.jpg

mRemoteDevice.beginConfigure();mRemoteDevice.endConfigure(operatingMode, null);中间的过程是IPC通知service端告知当前正在处理输入输出流。执行完mRemoteDevice.endConfigure(operatingMode, null);返回success = true;如果中间被终端了,那么success肯定不为true。

3.1 检查输入流

checkInputConfiguration(inputConfig);
当前inputConfig为null,所以这部分不执行。

3.2 检查输出流
  • 检查当前缓存的输出流数据列表,如果当前的输出流信息已经在列表中,则不必要重新创建流,如果没有则需要创建流。
            // Streams to create
            HashSet<OutputConfiguration> addSet = new HashSet<OutputConfiguration>(outputs);
            // Streams to delete
            List<Integer> deleteList = new ArrayList<Integer>();
            for (int i = 0; i < mConfiguredOutputs.size(); ++i) {
                int streamId = mConfiguredOutputs.keyAt(i);
                OutputConfiguration outConfig = mConfiguredOutputs.valueAt(i);
                if (!outputs.contains(outConfig) || outConfig.isDeferredConfiguration()) {
                    deleteList.add(streamId);
                } else {
                    addSet.remove(outConfig);  // Don't create a stream previously created
                }
            }

private final SparseArray<OutputConfiguration> mConfiguredOutputs = new SparseArray<>();
mConfiguredOutputs是内存中的输出流缓存列表,每次创建输出流都会把streamId和输出流缓存在这个SparseArray中。
这个部分代码操作完成之后:
addSet就是要即将要创建输出流的集合列表。
deleteList就是即将要删除的streamId列表,保证当前mConfiguredOutputs列表中的输出流数据是最新可用的。
下面是删除过期输出流的地方:

                // 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);
                        mConfiguredOutputs.put(streamId, outConfig);
                    }
                }
3.3 mRemoteDevice.createStream(outConfig)

根据《Android Camera进程间通信类总结》中分析的情况,这个IPC调用直接调用到CameraDeviceClient.h中的virtual binder::Status createStream( const hardware::camera2::params::OutputConfiguration &outputConfiguration, /*out*/ int32_t* newStreamId = NULL) override;
其实第一个参数outputConfiguration表示输出surface,第2个参数是out属性的,表示IPC执行之后返回的参数。该方法中主要就是下面的这段代码了。

    const std::vector<sp<IGraphicBufferProducer>>& bufferProducers =
            outputConfiguration.getGraphicBufferProducers();
    size_t numBufferProducers = bufferProducers.size();
//......
    for (auto& bufferProducer : bufferProducers) {
//......
        sp<Surface> surface;
        res = createSurfaceFromGbp(streamInfo, isStreamInfoValid, surface, bufferProducer,
                physicalCameraId);
//......
        surfaces.push_back(surface);
    }
//......
    int streamId = camera3::CAMERA3_STREAM_ID_INVALID;
    std::vector<int> surfaceIds;
    err = mDevice->createStream(surfaces, deferredConsumer, streamInfo.width,
            streamInfo.height, streamInfo.format, streamInfo.dataSpace,
            static_cast<camera3_stream_rotation_t>(outputConfiguration.getRotation()),
            &streamId, physicalCameraId, &surfaceIds, outputConfiguration.getSurfaceSetID(),
            isShared);

for循环中使用outputConfiguration.getGraphicBufferProducers()得到的GraphicBufferProducers创建出对应的surface,同时会对这些surface对象进行判断,检查它们的合法性,合法的话就会将它们加入到surfaces集合中,然后调用mDevice->createStream进一步执行流的创建。
这里就要说一说Android显示系统的一些知识了,大家要清楚,Android上最终绘制在屏幕上的buffer都是在显存中分配的,而除了这部分外,其他都是在内存中分配的,buffer管理的模块有两个,一个是framebuffer,一个是gralloc,framebuffer用来将渲染好的buffer显示到屏幕上,而gralloc用于分配buffer,我们相机预览的buffer轮转也不例外,它所申请的buffer根上也是由gralloc来分配的,在native层的描述是一个private_handle_t指针,而中间会经过多层的封装,这些buffer都是共享的。
只不过它的生命周期中的某个时刻只能属于一个所有者,而这些所有者的角色在不断的变换,这也就是Android中最经典的生产者--消费者的循环模型了,生产者就是BufferProducer,消费者就是BufferConsumer,每一个buffer在它的生命周期过程中转换时都会被锁住,这样它的所有者角色发生变化,而其他对象想要修改它就不可能了,这样就保证了buffer同步。


参考:https://blog.csdn.net/sinat_22657459/article/details/79370295


3.4 mDevice->createStream

首先将上一步传入的surface,也就是以后的consumer加入到队列中,然后调用重载的createStream方法进一步处理。这里的参数width就表示我们要配置的surface的宽度,height表示高度,format表示格式,这个format格式是根据surface查询ANativeWindow获取的——anw->query(anw, NATIVE_WINDOW_FORMAT, &format)我们前面已经说过,dataSpace的类型为android_dataspace,它表示我们buffer轮转时,buffer的大小,接下来定义一个Camera3OutputStream局部变量,这个也就是我们说的配置流了,接下来的if/else判断会根据我们的意图,创建不同的流对象,比如我们要配置拍照流,它的format格式为HAL_PIXEL_FORMAT_BLOB,所以就执行第一个if分支,创建一个Camera3OutputStream,创建完成后,执行*id = mNextStreamId++,给id指针赋值,这也就是当前流的id了,所以它是递增的。一般情况下,mStatus的状态在initializeCommonLocked()初始化通过调用internalUpdateStatusLocked方法被赋值为STATUS_UNCONFIGURED状态,所以这里的switch/case分支中就进入case STATUS_UNCONFIGURED,然后直接break跳出了,所以局部变量wasActive的值为false,最后直接返回OK。
到这里,createStream的逻辑就执行完成了,还是要提醒大家,createStream的逻辑是在framework中的for循环里执行的,我们的创建相当于只配置了一个surface,如果有多个surface的话,这里会执行多次,相应的Camera3OutputStream流的日志也会打印多次,这对于大家定位问题也非常有帮助。

3.5 mRemoteDevice.endConfigure
binder::Status CameraDeviceClient::endConfigure(int operatingMode,
        const hardware::camera2::impl::CameraMetadataNative& sessionParams) {
//......
    status_t err = mDevice->configureStreams(sessionParams, operatingMode);
//......
}

这里传入的第二个参与一般为null,调用到
Camera3Device::configureStreams
--->Camera3Device::filterParamsAndConfigureLocked
--->Camera3Device::configureStreamsLocked
Camera3Device::configureStreamsLocked中会直接调用HAL层的配置流方法:res = mInterface->configureStreams(sessionBuffer, &config, bufferSizes);

  • 完成输入流的配置
    if (mInputStream != NULL && mInputStream->isConfiguring()) {
        res = mInputStream->finishConfiguration();
        if (res != OK) {
            CLOGE("Can't finish configuring input stream %d: %s (%d)",
                    mInputStream->getId(), strerror(-res), res);
            cancelStreamsConfigurationLocked();
            return BAD_VALUE;
        }
    }
  • 完成输出流的配置
    for (size_t i = 0; i < mOutputStreams.size(); i++) {
        sp<Camera3OutputStreamInterface> outputStream =
            mOutputStreams.editValueAt(i);
        if (outputStream->isConfiguring() && !outputStream->isConsumerConfigurationDeferred()) {
            res = outputStream->finishConfiguration();
            if (res != OK) {
                CLOGE("Can't finish configuring output stream %d: %s (%d)",
                        outputStream->getId(), strerror(-res), res);
                cancelStreamsConfigurationLocked();
                return BAD_VALUE;
            }
        }
    }

outputStream->finishConfiguration()
--->Camera3Stream::finishConfiguration
--->Camera3OutputStream::configureQueueLocked
--->Camera3OutputStream::configureConsumerQueueLocked
关于一下Camera3OutputStream::configureConsumerQueueLocked核心的执行步骤:

    // Configure consumer-side ANativeWindow interface. The listener may be used
    // to notify buffer manager (if it is used) of the returned buffers.
    res = mConsumer->connect(NATIVE_WINDOW_API_CAMERA,
            /*listener*/mBufferReleasedListener,
            /*reportBufferRemoval*/true);
    if (res != OK) {
        ALOGE("%s: Unable to connect to native window for stream %d",
                __FUNCTION__, mId);
        return res;
    }

mConsumer就是配置时创建的surface,我们连接mConsumer,然后分配需要的空间大小。下面申请底层的ANativeWindow窗口,这是一个OpenCL 的窗口。对应的Android设备上一般是两种:Surface和SurfaceFlinger

    int maxConsumerBuffers;
    res = static_cast<ANativeWindow*>(mConsumer.get())->query(
            mConsumer.get(),
            NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS, &maxConsumerBuffers);
    if (res != OK) {
        ALOGE("%s: Unable to query consumer undequeued"
                " buffer count for stream %d", __FUNCTION__, mId);
        return res;
    }

至此,CameraDeviceImpl->configureStreamsChecked分析完成,接下里我们需要根据配置stream结果来创建CameraCaptureSession

4.CameraCaptureSessionImpl构造函数

            try {
                // configure streams and then block until IDLE
                configureSuccess = configureStreamsChecked(inputConfig, outputConfigurations,
                        operatingMode, sessionParams);
                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);
                }
            }

配置流完成之后,返回configureSuccess表示当前配置是否成功。
然后创建CameraCaptureSessionImpl的时候要用到:

            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 {
                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();

一般执行CameraCaptureSessionImpl构造函数。

    CameraCaptureSessionImpl(int id, Surface input,
            CameraCaptureSession.StateCallback callback, Executor stateExecutor,
            android.hardware.camera2.impl.CameraDeviceImpl deviceImpl,
            Executor deviceStateExecutor, boolean configureSuccess) {
        if (callback == null) {
            throw new IllegalArgumentException("callback must not be null");
        }

        mId = id;
        mIdString = String.format("Session %d: ", mId);

        mInput = input;
        mStateExecutor = checkNotNull(stateExecutor, "stateExecutor must not be null");
        mStateCallback = createUserStateCallbackProxy(mStateExecutor, callback);

        mDeviceExecutor = checkNotNull(deviceStateExecutor,
                "deviceStateExecutor must not be null");
        mDeviceImpl = checkNotNull(deviceImpl, "deviceImpl must not be null");
        mSequenceDrainer = new TaskDrainer<>(mDeviceExecutor, new SequenceDrainListener(),
                /*name*/"seq");
        mIdleDrainer = new TaskSingleDrainer(mDeviceExecutor, new IdleDrainListener(),
                /*name*/"idle");
        mAbortDrainer = new TaskSingleDrainer(mDeviceExecutor, new AbortDrainListener(),
                /*name*/"abort");
        if (configureSuccess) {
            mStateCallback.onConfigured(this);
            if (DEBUG) Log.v(TAG, mIdString + "Created session successfully");
            mConfigureSuccess = true;
        } else {
            mStateCallback.onConfigureFailed(this);
            mClosed = true; // do not fire any other callbacks, do not allow any other work
            Log.e(TAG, mIdString + "Failed to create capture session; configuration failed");
            mConfigureSuccess = false;
        }
    }

构造函数执行的最后可以看到,当前configureSuccess=true,执行mStateCallback.onConfigureFailed(this),如果失败,执行mStateCallback.onConfigureFailed(this)回调。

小结

createCaptureSession的过程就分析完了,它是我们相机预览最重要的条件,一般session创建成功,那么我们的预览就会正常,session创建失败,则预览一定黑屏,大家如果有碰到相机黑屏的问题,最大的疑点就是这里,session创建完成后,framework会通过CameraCaptureSession.StateCallback类的public abstract void onConfigured(@NonNull CameraCaptureSession session)回调到应用层,通知我们session创建成功了,那么我们就可以使用回调方法中的CameraCaptureSession参数,调用它的setRepeatingRequest方法来下预览了,该逻辑执行完成后,我们相机的预览就起来了。

推荐阅读更多精彩内容

  • 转载请注明出处(https://www.jianshu.com/p/5f538820e370),您的打赏是小编继续...
    字节串动阅读 13,421评论 6 37
  • 这篇文章本来上个月就要写的,后来一直没写,正好在新年写下,对之前的工作进行总结。 之前工作中需要自定义相机,网上看...
    zero_sr阅读 2,678评论 0 8
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 144,035评论 18 618
  • 本篇文章是基于谷歌有关Graphic的一篇概览文章的翻译:http://source.android.com/de...
    lee_3do阅读 4,384评论 2 17
  • 今天见了高睿和他做VR的投资团队,一起吃火锅聊Vito项目。 观察到自己在这样的场合会特别的放纵,不够收敛和有静气...
    黎海森阅读 57评论 0 0