1. 程式人生 > >Android Camera原理之createCaptureSession模組

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方法來下預覽了,該邏輯執行完成後,我們相機的預覽就起來了。
-----------------