主頁 > 移動端開發 > [Android P]OpenCamera詳細分析(Camera2+Hal3)

[Android P]OpenCamera詳細分析(Camera2+Hal3)

2021-10-24 09:48:49 移動端開發

這幾天休假,因為新作業涉及到Android Camera系統的問題,本文就整理了在Android P上講解OpenCamera比較詳細的文章,結合Andriod P原始碼,以架構圖、UML順序圖、UML類圖和關鍵代碼走讀的方式,從App層、Framework層、Hal層詳細分析了OpenCamera的流程,如有分析的不對的地方,歡迎大家指正~

注意:Camera系統架構采用Camera2+Hal3,

參考博客:

Android Camera2+HAL3架構_既不是天才,便做那瘋子!-CSDN博客

[Android O] HAL3 之 Open Camera2 流程(零)—— 概覽_小石不識月,呼作白玉盤,-CSDN博客

Android P之Camera HAL3流程分析(0)_Vincentywj的博客-CSDN博客

AndroidO Treble架構(二):CameraProvider 及其守護行程_QGov的博客-CSDN博客

1 Camera2+Hal3整體架構

先看下Camera2+Hal3的整體架構:

圖1-1 Android Camera2+Hal3整體架構圖

上圖歸納的關鍵資訊是2個介面和3個層次,
2個介面是指aidl介面和hidl介面,aidl介面包含ICameraDeviceUser,ICameraDeviceCallbacks,ICameraService和ICameraServiceListener,其中ICameraDeviceCallbacks和ICameraServiceListener是回呼介面,hidl介面包含ICameraDeviceSession,ICameraDevice和ICameraProvider,
3個層次從上到下依次是app層,framework層和hal層,app層的行程是app(camera client)行程,framework層的行程是camera server行程,hal層的行程是hal(camera provider)行程,其中app(camera client)和camera server通信使用 AIDL(Android Interface Definition Language) ,camera server和hal(camera provider)行程通信使用HIDL(HAL interface definition language) ,

2 分層分析

本節按照如下順序進行詳細分析:
2.1 camera server行程和hal(camera provider)行程的啟動
2.2 app(camera client)行程<-->camera server行程
2.3 camera server行程<-->hal(camera provider)行程
2.4 camera hal分析

2.1 camera server行程和hal(camera provider)行程的啟動

我們知道app(camera client)行程是在用戶打開相機時啟動的,而camera server行程和hal(camera provider)行程是在Android系統開機時就準備好了,
下圖總結了一下camera server行程與hal(camera provider)行程啟動并初始化的關鍵邏輯:

圖2-1-1 camera server行程與hal(camera provider)行程啟動架構圖

圖中兩點關鍵資訊:
1 hal(camera provider)行程啟動,實體化CameraProvider,并注冊服務到HwServiceManager;
實體化CameraProvider時會實體化CameraModule,CameraModule用來與Camera HAL進行互動,
2 camera server行程啟動,初始化,并從ICameraProvider和HwServiceManager中獲取camera provider服務;
CameraService類通過CameraProviderManager類來管理對CameraProvider的操作,

2.1.1 camera provider行程的啟動

camera provider行程的主要作業是注冊CameraProvider服務,以便camera server啟動時能找到它,下圖是camera provider行程的啟動UML順序圖,后面按照此圖分析關鍵代碼:

圖2-1-2 camera provider行程的啟動UML順序圖

Step1 provider.rc
啟動腳本
檔案:"hardware/interfaces/camera/provider/2.4/default/android.hardware.camera.provider@2.4-service_64.rc"

service vendor.camera-provider-2-4 /vendor/bin/hw/android.hardware.camera.provider@2.4-service_64
    class hal
    user cameraserver
    group audio camera input drmrpc
    ioprio rt 4
    capabilities SYS_NICE
    writepid /dev/cpuset/camera-daemon/tasks /dev/stune/top-app/tasks

camera provider守護行程由init行程啟動,
Step2 main
檔案:"hardware/interfaces/camera/provider/2.4/default/service.cpp"

int main()
{
    ALOGI("Camera provider Service is starting.");
    // The camera HAL may communicate to other vendor components via
    // /dev/vndbinder
    // Step 3
    android::ProcessState::initWithDriver("/dev/vndbinder");
    // Step 4
    return defaultPassthroughServiceImplementation<ICameraProvider>("legacy/0", /*maxThreads*/ 6);
}

先與"/dev/vndbinder"進行某種關聯,表明Camera provider Service會通過它與其它 vendor 組件進行通信,然后創建默認為直通模式(passthrough)的 CameraProvider服務,
Step3 initWithDriver
檔案:"frameworks/native/libs/binder/ProcessState.cpp"

sp<ProcessState> ProcessState::initWithDriver(const char* driver)
{
    Mutex::Autolock _l(gProcessMutex);
    if (gProcess != NULL) {
        // Allow for initWithDriver to be called repeatedly with the same
        // driver.
        if (!strcmp(gProcess->getDriverName().c_str(), driver)) {
            return gProcess;
        }
        LOG_ALWAYS_FATAL("ProcessState was already initialized.");
    }

    if (access(driver, R_OK) == -1) {
        ALOGE("Binder driver %s is unavailable. Using /dev/binder instead.", driver);
        driver = "/dev/binder";
    }
    // 3.1
    gProcess = new ProcessState(driver);
    return gProcess;
}

initWithDriver是ProcessState的static函式,這里以driver的名字("/dev/vndbinder")實體化android::ProcessState,
3.1 new ProcessState
檔案:"frameworks/native/libs/binder/ProcessState.cpp"

ProcessState::ProcessState(const char *driver)
    : mDriverName(String8(driver))
    , mDriverFD(open_driver(driver))
    ...
{
    if (mDriverFD >= 0) {
        // mmap the binder, providing a chunk of virtual address space to receive transactions.
        mVMStart = mmap(0, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0);
        if (mVMStart == MAP_FAILED) {
            // *sigh*
            ALOGE("Using %s failed: unable to mmap transaction memory.\n", mDriverName.c_str());
            close(mDriverFD);
            mDriverFD = -1;
            mDriverName.clear();
        }
    }

    LOG_ALWAYS_FATAL_IF(mDriverFD < 0, "Binder driver could not be opened.  Terminating.");
}

實體化ProcessState時,主要作業是打開檔案"dev/vndbinder"并mmap映射記憶體,將內核虛擬記憶體空間映射到用戶空間,
注意,這里的ProcessState類的命名空間是andriod::ProcessState,
Step4 defaultPassthroughServiceImplementation
檔案:"system/libhidl/transport/include/hidl/LegacySupport.h"

/**
* Creates default passthrough service implementation. This method never returns.
*
* Return value is exit status.
*/
template<class Interface>
__attribute__((warn_unused_result))
status_t defaultPassthroughServiceImplementation(std::string name,
                                            size_t maxThreads = 1) {
    // Step 5
    configureRpcThreadpool(maxThreads, true);
    // Step 6
    status_t result = registerPassthroughServiceImplementation<Interface>(name);

    if (result != OK) {
        return result;
    }
    // Step 7
    joinRpcThreadpool();
    return UNKNOWN_ERROR;
}

注意:此時函式的命名空間是android::hardware::defaultPassthroughServiceImplementation,
配置RPC執行緒池并將Interface(ICameraProvider)以入參"legacy/0"為名注冊到相應的管理服務中,
Step5 configureRpcThreadpool
檔案:"system/libhidl/transport/HidlTransportSupport.cpp"

void configureRpcThreadpool(size_t maxThreads, bool callerWillJoin) {
    // TODO(b/32756130) this should be transport-dependent
    // 5.1
    configureBinderRpcThreadpool(maxThreads, callerWillJoin);
}

5.1 configureBinderRpcThreadpool
檔案:"system/libhidl/transport/HidlBinderSupport.cpp"

void configureBinderRpcThreadpool(size_t maxThreads, bool callerWillJoin) {
    // 5.2, 5.3
    ProcessState::self()->setThreadPoolConfiguration(maxThreads, callerWillJoin /*callerJoinsPool*/);
}

注意,這里ProcessState類的命名空間是andriod::hardware::ProcessState,
5.2 ProcessState::self()
檔案:"system/libhwbinder/ProcessState.cpp"
ProcessState::self()這個函式最后會open("/dev/hwbinder"),

sp<ProcessState> ProcessState::self()
{
    Mutex::Autolock _l(gProcessMutex);
    if (gProcess != NULL) {
        return gProcess;
    }
    gProcess = new ProcessState(DEFAULT_BINDER_VM_SIZE);
    return gProcess;
}

ProcessState::ProcessState(size_t mmap_size)
    : mDriverFD(open_driver())
    , mVMStart(MAP_FAILED)
    ...
{
    if (mDriverFD >= 0) {
        // mmap the binder, providing a chunk of virtual address space to receive transactions.
        mVMStart = mmap(0, mMmapSize, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0);
        if (mVMStart == MAP_FAILED) {
            // *sigh*
            ALOGE("Using /dev/hwbinder failed: unable to mmap transaction memory.\n");
            close(mDriverFD);
            mDriverFD = -1;
        }
    }
    else {
        ALOGE("Binder driver could not be opened.  Terminating.");
    }
}

static int open_driver()
{
    int fd = open("/dev/hwbinder", O_RDWR | O_CLOEXEC);
    if (fd >= 0) {
        int vers = 0;
        status_t result = ioctl(fd, BINDER_VERSION, &vers);
        if (result == -1) {
            ALOGE("Binder ioctl to obtain version failed: %s", strerror(errno));
            close(fd);
            fd = -1;
        }
        ...
    } else {
        ALOGW("Opening '/dev/hwbinder' failed: %s\n", strerror(errno));
    }
    return fd;
}

5.3 setThreadPoolConfiguration
檔案:"system/libhwbinder/ProcessState.cpp"

status_t ProcessState::setThreadPoolConfiguration(size_t maxThreads, bool callerJoinsPool) {
    LOG_ALWAYS_FATAL_IF(maxThreads < 1, "Binder threadpool must have a minimum of one thread.");
    status_t result = NO_ERROR;
    // the BINDER_SET_MAX_THREADS ioctl really tells the kernel how many threads
    // it's allowed to spawn, *in addition* to any threads we may have already
    // spawned locally. If 'callerJoinsPool' is true, it means that the caller
    // will join the threadpool, and so the kernel needs to create one less thread.
    // If 'callerJoinsPool' is false, we will still spawn a thread locally, and we should
    // also tell the kernel to create one less thread than what was requested here.
    size_t kernelMaxThreads = maxThreads - 1;
    if (ioctl(mDriverFD, BINDER_SET_MAX_THREADS, &kernelMaxThreads) != -1) {
        AutoMutex _l(mLock);
        mMaxThreads = maxThreads;
        mSpawnThreadOnStart = !callerJoinsPool;
    } else {
        result = -errno;
        ALOGE("Binder ioctl to set max threads failed: %s", strerror(-result));
    }
    return result;
}

這里的mDriverFD是指/dev/hwbinder,ioctl下發BINDER_SET_MAX_THREADS命令給"dev/hwbinder"的驅動,
Step6 registerPassthroughServiceImplementation
檔案:"system/libhidl/transport/include/hidl/LegacySupport.h"

/**
* Registers passthrough service implementation.
*/
template<class Interface>
__attribute__((warn_unused_result))
status_t registerPassthroughServiceImplementation(
        std::string name = "default") {
    // 6.1
    sp<Interface> service = Interface::getService(name, true /* getStub */);
    if (service == nullptr) {
        ALOGE("Could not get passthrough implementation for %s/%s.",
            Interface::descriptor, name.c_str());
        return EXIT_FAILURE;
    }
    LOG_FATAL_IF(service->isRemote(), "Implementation of %s/%s is remote!",
            Interface::descriptor, name.c_str());
    // 6.2
    status_t status = service->registerAsService(name);
    if (status == OK) {
        ALOGI("Registration complete for %s/%s.",
            Interface::descriptor, name.c_str());
    } else {
        ALOGE("Could not register service %s/%s (%d).",
            Interface::descriptor, name.c_str(), status);
    }

    return status;
}

入參name為"legacy/0",模板類引數Interface為ICameraProvider,
這個函式做了兩件事:
1 實體化CameraProvider物件
2 注冊CameraProvider服務到HwServiceManager
getService()和registerAsService()是CameraProviderAll.cpp里的函式,CameraProviderAll.cpp是hidl機制通過ICameraProvider.hal編譯生成的,
ICameraProvider.hal:hardware/interfaces/camera/provider/2.4/ICameraProvider.hal

6.1 ICameraProvider::getService
當getStub=ture時,getService是以passthrough模式打開 HAL 實作,得到的是CameraProvider的實體化物件(BnHwCameraProvider),當getStub=false時,得到的是binder代理物件(BpHwCameraProvider),后面camera server行程會用到,
詳細分析請參考:
https://blog.csdn.net/wzoxylwzoxyl/article/details/82227506
最后會呼叫到HIDL_FETCH_ICameraProvider函式,
6.1.1 HIDL_FETCH_ICameraProvider
檔案:"./hardware/interfaces/camera/provider/2.4/default/CameraProvider.cpp"

ICameraProvider* HIDL_FETCH_ICameraProvider(const char* name) {
    if (strcmp(name, kLegacyProviderName) == 0) {
        // 6.1.2
        CameraProvider* provider = new CameraProvider();
        if (provider == nullptr) {
            ALOGE("%s: cannot allocate camera provider!", __FUNCTION__);
            return nullptr;
        }
        if (provider->isInitFailed()) {
            ALOGE("%s: camera provider init failed!", __FUNCTION__);
            delete provider;
            return nullptr;
        }
        return provider;
    } else if (strcmp(name, kExternalProviderName) == 0) {
        ExternalCameraProvider* provider = new ExternalCameraProvider();
        return provider;
    }
    ALOGE("%s: unknown instance name: %s", __FUNCTION__, name);
    return nullptr;
}

CameraProvider::CameraProvider() :
        camera_module_callbacks_t({sCameraDeviceStatusChange,
                                   sTorchModeStatusChange}) {
    // 6.1.3 
    mInitFailed = initialize();
}

bool CameraProvider::initialize() {
    camera_module_t *rawModule;
    // 6.1.4 "hardware/libhardware/hardware.c"
    int err = hw_get_module(CAMERA_HARDWARE_MODULE_ID,
            (const hw_module_t **)&rawModule);
    if (err < 0) {
        ALOGE("Could not load camera HAL module: %d (%s)", err, strerror(-err));
        return true;
    }
    // 6.1.5 "hardware/interfaces/camera/common/1.0/default/CameraModule.cpp"
    mModule = new CameraModule(rawModule);
    // 6.1.6
    err = mModule->init();
    if (err != OK) {
        ALOGE("Could not initialize camera HAL module: %d (%s)", err, strerror(-err));
        mModule.clear();
        return true;
    }
    ALOGI("Loaded \"%s\" camera module", mModule->getModuleName());
    ...
}

6.1.2:創建一個 CameraProvider 實體
6.1.4:通過 hw_get_module 函式獲取到rawModule的實體(從相應的 Camera HAL 動態庫中加載得到,hw_get_module->hw_get_module_by_class->load->dlopen),rawModule是連接到 HAL 層的關鍵結構,通過它就可以呼叫到 HAL 中的一些函式,CAMERA_HARDWARE_MODULE_ID為字串"camera",
6.1.5:基于 rawModule 創建 CameraModule 實體并初始化,之后都是通過 mModule 來對 HAL 進行操作的,CameraModule 是對于 camera_module_t 的一層封裝,諸如 init、open 這樣的操作,實際上都是通過呼叫 camera_module_t 結構中函式指標來完成的,
6.2 registerAsService
檔案:out/.../CameraProviderAll.cpp

::android::status_t ICameraProvider::registerAsService(const std::string &serviceName) {
    ::android::hardware::details::onRegistration("android.hardware.camera.provider@2.4", "ICameraProvider", serviceName);
    // 得到 BpHwServiceManager 物件,就是HwServiceManager物件的binder代理
    const ::android::sp<::android::hidl::manager::V1_0::IServiceManager> sm
            = ::android::hardware::defaultServiceManager();
    // 呼叫 BpHwServiceManager::add() 注冊服務
    // 6.2.1
    ::android::hardware::Return<bool> ret = sm->add(serviceName.c_str(), this);
    return ret.isOk() && ret ? ::android::OK : ::android::UNKNOWN_ERROR;
}

將CameraProvider注冊為一個服務,其他行程需要使用camera hal時通過binder得到CameraProvider代理類(BpHwCameraProvider)即可操作camera hal ,注意,是將CameraProvider服務注冊到HwServiceManager中,不是ServiceManager,
Step7 joinRpcThreadpool
檔案:"system/libhidl/transport/HidlTransportSupport.cpp"

void joinRpcThreadpool() {
    // TODO(b/32756130) this should be transport-dependent
    // 7.1
    joinBinderRpcThreadpool();
}

CameraProvider服務進入回圈,等待binder請求并處理,這里的joinRpcThreadpool()是把主執行緒也放入執行緒池中等待請求,防止這個行程退出,
7.1 joinBinderRpcThreadpool
檔案:"system/libhidl/transport/HidlBinderSupport.cpp"

void joinBinderRpcThreadpool() {
    // 7.2
    IPCThreadState::self()->joinThreadPool();
}

7.2 joinThreadPool
檔案:"system/libhwbinder/IPCThreadState.cpp"

void IPCThreadState::joinThreadPool(bool isMain)
{
    LOG_THREADPOOL("**** THREAD %p (PID %d) IS JOINING THE THREAD POOL\n", (void*)pthread_self(), getpid());
    mOut.writeInt32(isMain ? BC_ENTER_LOOPER : BC_REGISTER_LOOPER);
    status_t result;
    // 回圈處理請求
    do {
        processPendingDerefs();
        // now get the next command to be processed, waiting if necessary
        result = getAndExecuteCommand();
        if (result < NO_ERROR && result != TIMED_OUT && result != -ECONNREFUSED && result != -EBADF) {
            ALOGE("getAndExecuteCommand(fd=%d) returned unexpected error %d, aborting",
                  mProcess->mDriverFD, result);
            abort();
        }
        // Let this thread exit the thread pool if it is no longer
        // needed and it is not the main process thread.
        if(result == TIMED_OUT && !isMain) {
            break;
        }
    } while (result != -ECONNREFUSED && result != -EBADF);

    LOG_THREADPOOL("**** THREAD %p (PID %d) IS LEAVING THE THREAD POOL err=%d\n",
        (void*)pthread_self(), getpid(), result);
    // 如果走到這表明出現了一些錯誤,需要告訴驅動這個執行緒不再處理訊息了,即退出LOOPER
    mOut.writeInt32(BC_EXIT_LOOPER);
    talkWithDriver(false);
}

getAndExecuteCommand()會處理command,必要時阻塞執行緒,
分析完camera provider行程的啟動,我們再來看camera server行程的啟動,

2.1.2 camera server行程的啟動

camera provider行程先啟動,然后camera server行程再啟動,camera server行程啟動后會去 "連接" CameraProvider服務,從HwServiceManager里獲取CameraProvider服務,

圖2-1-3 camera server行程的啟動UML順序圖

Step1 cameraserver.rc
啟動腳本
檔案:"frameworks/av/camera/cameraserver/cameraserver.rc"

service cameraserver /system/bin/cameraserver
    class main
    user cameraserver
    group audio camera input drmrpc
    ioprio rt 4
    writepid /dev/cpuset/camera-daemon/tasks /dev/stune/top-app/tasks

camera server行程由init行程啟動,
Step2 main
檔案:"frameworks/av/camera/cameraserver/main_cameraserver.cpp"

using namespace android;
int main(int argc __unused, char** argv __unused)
{
    signal(SIGPIPE, SIG_IGN);

    // Set 3 threads for HIDL calls
    // open(/dev/hwbinder),用于和BnHwServiceManager通信
    // 2.1
    hardware::configureRpcThreadpool(3, /*willjoin*/ false);

    // open(/dev/binder),用于和BnServiceManager通信
    // 2.2
    sp<ProcessState> proc(ProcessState::self());
    // 獲得ServiceManager的代理類BpServiceManager, 注意不是HwServiceManager的代理類
    sp<IServiceManager> sm = defaultServiceManager();
    ALOGI("ServiceManager: %p", sm.get());

    // 做了兩件事 (1)得到CameraService實體化物件 (2)注冊CameraService服務
    // Step 3
    CameraService::instantiate();
    ProcessState::self()->startThreadPool();
    IPCThreadState::self()->joinThreadPool();
}

注意,命名空間是android,
todo signal信號干嘛用的?
注意,defaultServiceManager()獲得的是ServiceManager不是HwServiceManager,即CameraService服務是注冊在ServiceManager里的,
defaultServiceManager的實作容易混淆,系統中有兩個實作,在呼叫時需要注意方法所在的域:
android:defaultServiceManager可以獲的BpServiceManager,
android:hardware:defaultServiceManager可以獲的BpHwServiceManager,
Step3 instantiate
檔案:"frameworks/native/libs/binder/include/binder/BinderService.h"

static void instantiate() { publish(); }

static status_t publish(bool allowIsolated = false,
                       int dumpFlags = IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT) {
    sp<IServiceManager> sm(defaultServiceManager());
    // 3.1,3.2
    return sm->addService(String16(SERVICE::getServiceName()), new SERVICE(), allowIsolated,
                          dumpFlags);
}

這個 instantiate() 介面并不是定義在 CameraService 類中的,而是定義在 BinderService 類里(CameraService 繼承了它),在此處,它的作用是創建一個 CameraService實體,并將其注冊到 ServiceManager 中,
3.1 new CameraService
SERVICE為CameraService,
3.2 addService
檔案:"frameworks/native/libs/binder/IServiceManager.cpp"

virtual status_t addService(const String16& name, const sp<IBinder>& service,
                            bool allowIsolated, int dumpsysPriority) {
    Parcel data, reply;
    data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor());
    data.writeString16(name);
    data.writeStrongBinder(service);
    data.writeInt32(allowIsolated ? 1 : 0);
    data.writeInt32(dumpsysPriority);
    status_t err = remote()->transact(ADD_SERVICE_TRANSACTION, data, &reply);
    return err == NO_ERROR ? reply.readExceptionCode() : err;
}

注意,在這一程序中CameraService被sp強指標參考了,通過UML類圖可以看出CameraService實際是IBinder的子類,
ICameraService.aidl:./frameworks/av/camera/aidl/android/hardware/ICameraService.aidl
ICameraService.aidl會通過aidl編譯生成ICameraService.h,

圖2-1-4 ICameraService UML類圖

Step4 onFirstRef
檔案:"frameworks/av/services/camera/libcameraservice/CameraService.cpp"
首次被sp強指標參考時,就會呼叫 onFirstRef() 函式,

void CameraService::onFirstRef()
{
    ALOGI("CameraService process starting");
    BnCameraService::onFirstRef();

    // Update battery life tracking if service is restarting
    BatteryNotifier& notifier(BatteryNotifier::getInstance());
    notifier.noteResetCamera();
    notifier.noteResetFlashlight();

    status_t res = INVALID_OPERATION;
    // Step 5
    res = enumerateProviders();
    if (res == OK) {
        mInitialized = true;
    }

    CameraService::pingCameraServiceProxy();
    mUidPolicy = new UidPolicy(this);
    mUidPolicy->registerSelf();
}

列舉Providers,
Step5 enumerateProviders
檔案:"frameworks/av/services/camera/libcameraservice/CameraService.cpp"

status_t CameraService::enumerateProviders() {
    status_t res;

    std::vector<std::string> deviceIds;
    {
        Mutex::Autolock l(mServiceLock);

        if (nullptr == mCameraProviderManager.get()) {
            // Step 6
            mCameraProviderManager = new CameraProviderManager();
            // Step 7
            res = mCameraProviderManager->initialize(this);
            if (res != OK) {
                ALOGE("%s: Unable to initialize camera provider manager: %s (%d)",
                        __FUNCTION__, strerror(-res), res);
                return res;
            }
        }
        ...
        deviceIds = mCameraProviderManager->getCameraDeviceIds();
    }

    for (auto& cameraId : deviceIds) {
        String8 id8 = String8(cameraId.c_str());
        onDeviceStatusChanged(id8, CameraDeviceStatus::PRESENT);
    }

    return OK;
}

將 CameraProviderManager 實體化,然后呼叫 initialize() 介面初始化,傳入的引數是 this 指標,指向當前CameraService實體的地址,
Step6 new CameraProviderManager()
檔案:"frameworks/av/services/camera/libcameraservice/common/CameraProviderManager.cpp"
Step7 initialize
檔案:"frameworks/av/services/camera/libcameraservice/common/CameraProviderManager.h"
先看下函式宣告:

/**
* Initialize the manager and give it a status listener; optionally accepts a service
* interaction proxy.
*
* The default proxy communicates via the hardware service manager; alternate proxies can be
* used for testing. The lifetime of the proxy must exceed the lifetime of the manager.
*/
status_t initialize(wp<StatusListener> listener,
        ServiceInteractionProxy *proxy = &sHardwareServiceInteractionProxy);

wp<StatusListener> listener是CameraService 實體,采用的是默認代理,默認代理有什么作用?(Step 9)
檔案:"frameworks/av/services/camera/libcameraservice/common/CameraProviderManager.cpp"

status_t CameraProviderManager::initialize(wp<CameraProviderManager::StatusListener> listener,
        ServiceInteractionProxy* proxy) {
    std::lock_guard<std::mutex> lock(mInterfaceMutex);
    if (proxy == nullptr) {
        ALOGE("%s: No valid service interaction proxy provided", __FUNCTION__);
        return BAD_VALUE;
    }
    // listener是CameraService 物件
    mListener = listener;
    // proxy是sHardwareServiceInteractionProxy
    mServiceProxy = proxy;

    // Registering will trigger notifications for all already-known providers
    bool success = mServiceProxy->registerForNotifications(
        /* instance name, empty means no filter */ "",
        this);
    if (!success) {
        ALOGE("%s: Unable to register with hardware service manager for notifications "
                "about camera providers", __FUNCTION__);
        return INVALID_OPERATION;
    }

    // See if there's a passthrough HAL, but let's not complain if there's not
    // Step 8
    addProviderLocked(kLegacyProviderName, /*expected*/ false);
    addProviderLocked(kExternalProviderName, /*expected*/ false);

    return OK;
}

通過服務代理作出一個注冊動作,根據注釋,注冊會觸發一個給所有已知 Provider 進行通知的動作,傳入的引數 kLegacyProviderName,為字串"legacy/0",

Step8 addProviderLocked
檔案:"frameworks/av/services/camera/libcameraservice/common/CameraProviderManager.cpp"

status_t CameraProviderManager::addProviderLocked(const std::string& newProvider, bool expected) {
    for (const auto& providerInfo : mProviders) {
        if (providerInfo->mProviderName == newProvider) {
            ALOGW("%s: Camera provider HAL with name '%s' already registered", __FUNCTION__,
                    newProvider.c_str());
            return ALREADY_EXISTS;
        }
    }

    sp<provider::V2_4::ICameraProvider> interface;
    // 上面分析過mServiceProxy=sHardwareServiceInteractionProxy,這里getService(newProvider)其實就是得到CameraProvider的代理類(BpHwCameraProvider)
    // 8.1
    interface = mServiceProxy->getService(newProvider);
    if (interface == nullptr) {
        if (expected) {
            ALOGE("%s: Camera provider HAL '%s' is not actually available", __FUNCTION__,
                    newProvider.c_str());
            return BAD_VALUE;
        } else {
            return OK;
        }
    }
    // 8.2
    sp<ProviderInfo> providerInfo = new ProviderInfo(newProvider, interface, this);
    status_t res = providerInfo->initialize();
    if (res != OK) {
        return res;
    }

    mProviders.push_back(providerInfo);
    return OK;
}

檢查已知的 Provider 中是否已有名為 legacy/0 的,
根據 legacy/0 從服務代理處獲取 CameraProvider 介面,這里getService(newProvider)其實就是得到CameraProvider的代理類(BpHwCameraProvider),采用的是默認代理即sHardwareServiceInteractionProxy,
通過ProviderInfo來保存當前 Provider相關資訊,
8.1 getService
檔案:"frameworks/av/services/camera/libcameraservice/common/CameraProviderManager.h"

// Standard use case - call into the normal generated static methods which invoke
// the real hardware service manager
struct HardwareServiceInteractionProxy : public ServiceInteractionProxy {
    ...
    virtual sp<hardware::camera::provider::V2_4::ICameraProvider> getService(
            const std::string &serviceName) override {
        // getStub的默認值為flase,之前分析過getStub = flase 會得到 CameraProvider 的代理類(BpHwCameraProvider)
        // 8.1.1
        return hardware::camera::provider::V2_4::ICameraProvider::getService(serviceName);
    }
};

getService的實作在hidl編譯生成的CameraProviderAll.cpp里,
小結:framework的上層通過ServiceManager(/dev/binder)得到CameraService服務,而CameraService通過HwServiceManager(/dev/hwbinder)得到 CameraProvider 服務,而CameraProvider可以訪問Camera HAL,這樣上層 framework 就能夠訪問 Camera HAL 了,

2.1.3 總結流程

1 camera provider行程啟動,實體化CameraProvider,并將CameraProvider服務注冊到HwServiceManager;
在CameraProvider初始化程序中,從動態庫中加載了HAL層的關鍵結構(camera_module_t),并將其封裝到CameraModule中,
2 camera server行程啟動,實體化CameraService,并將CameraService服務注冊到ServiceManager,
3 由于強指標首次參考,CameraService::onFirstRef()被呼叫,進行初始化;在CameraService初始化程序中,通過CameraProviderManager來獲取HwServiceManager中已注冊的CameraProvider服務,獲取BpHwCameraProvider實體,
這樣,framework的上層通過ServiceManager(/dev/binder)得到CameraService服務,而CameraService通過HwServiceManager(/dev/hwbinder)得到 CameraProvider 服務,而CameraProvider可以訪問Camera HAL,這樣上層 framework 就能夠訪問 Camera HAL 了,

2.2 app(camera client)行程<-->camera server行程

打開相機的流程就是打通 APP 到相機設備之間的連路的程序,按照Android架構從上到下依次打通下行控制路線,并通過設定回呼來構建上行的狀態、資料路線,
下圖是OpenCamera總體架構圖,黑色虛線是下行路線,紅色虛線是上行路線:

圖2-2-1 OpenCamera總體架構圖

上圖包含了三層的內容:
1 app(camera client)行程
2 camera server行程
3 hal(camera provider)行程
2.1節中我們已經分析了camera provider行程和camera server行程的啟動和初始化程序,下面我們按照如下三個內容繼續分析:
1 app(camera client)行程<-->camera server行程(2.2節)
2 camera server行程<-->hal(camera provider)行程(2.3節)
3 camera hal(2.4節)

本節(2.2節)詳細分析app(camera client)行程<-->camera server行程這個程序,這一部分主要的函式呼叫邏輯如下圖所示,

圖2-2-2 app(camera client)行程<-->camera server行程架構圖

下圖是app(camera client)行程<-->camera server行程 UML順序圖,根據UML順序圖分析原始碼:

圖2-2-3 app(camera client)行程<-->camera server行程 UML順序圖

Step1 openCamera
檔案:"./frameworks/base/core/java/android/hardware/camera2/CameraManager.java"

@RequiresPermission(android.Manifest.permission.CAMERA)
public void openCamera(@NonNull String cameraId,
        @NonNull final CameraDevice.StateCallback callback, @Nullable Handler handler)
        throws CameraAccessException {
    // 1.1
    openCameraForUid(cameraId, callback, CameraDeviceImpl.checkAndWrapHandler(handler),
                USE_CALLING_UID);
}

public void openCameraForUid(@NonNull String cameraId,
            @NonNull final CameraDevice.StateCallback callback, @NonNull Executor executor,
            int clientUid)
            throws CameraAccessException {
    ...
    // 1.2
    openCameraDeviceUserAsync(cameraId, callback, executor, clientUid);
}

app行程里打開相機:mCameraManager.openCamera,最后呼叫openCameraDeviceUserAsync,
1.2 openCameraDeviceUserAsync
檔案:"./frameworks/base/core/java/android/hardware/camera2/CameraManager.java"

    private CameraDevice openCameraDeviceUserAsync(String cameraId,
            CameraDevice.StateCallback callback, Executor executor, final int uid)
            throws CameraAccessException {
        CameraCharacteristics characteristics = getCameraCharacteristics(cameraId);
        CameraDevice device = null;

        synchronized (mLock) {
            ICameraDeviceUser cameraUser = null;
            // 應用端的相機物件類
            // Step2
            android.hardware.camera2.impl.CameraDeviceImpl deviceImpl =
                    new android.hardware.camera2.impl.CameraDeviceImpl(
                        cameraId,
                        callback,
                        executor,
                        characteristics,
                        mContext.getApplicationInfo().targetSdkVersion);
            // 應用端的回呼函式
            ICameraDeviceCallbacks callbacks = deviceImpl.getCallbacks();
            try {
                if (supportsCamera2ApiLocked(cameraId)) {
                    // Use cameraservice's cameradeviceclient implementation for HAL3.2+ devices
                    // Step3
                    ICameraService cameraService = CameraManagerGlobal.get().getCameraService();
                    if (cameraService == null) {
                        throw new ServiceSpecificException(
                            ICameraService.ERROR_DISCONNECTED,
                            "Camera service is currently unavailable");
                    }
                    // 通過cameraService跨行程獲得BpCameraDeviceUser物件
                    // Step4
                    cameraUser = cameraService.connectDevice(callbacks, cameraId,
                            mContext.getOpPackageName(), uid);
                } else {
                    ...
                }
            } catch (ServiceSpecificException e) {
                ...
            }

            // TODO: factor out callback to be non-nested, then move setter to constructor
            // For now, calling setRemoteDevice will fire initial
            // onOpened/onUnconfigured callbacks.
            // This function call may post onDisconnected and throw CAMERA_DISCONNECTED if
            // cameraUser dies during setup.
            // 保存CameraDeviceClient物件到CameraDeviceImpl,觸發應用的回呼函式
            // Step5
            deviceImpl.setRemoteDevice(cameraUser);
            device = deviceImpl;
        }
        return device;
    }

Step2,首先實體化一個android.hardware.camera2.impl.CameraDeviceImpl物件,值得注意的是,構造時傳入了 CameraDevice.StateCallback 以及 Handler,注意,CameraDeviceImpl類是CameraDevice類的子類,這兩個類是app層用來管理camera設備的,
Step3,通過CameraManagerGlobal獲取CameraService的本地介面(BpCameraService),(即圖2-1-4 ICameraService UML類圖BpCameraService),
todo ICameraService.Stub與BpCameraService是如何通過aidl聯系上的?
Step4,通過BpCameraService遠端呼叫connectDevice 法連接到相機設備,注意回傳的cameraUser實際上指向的是遠端CameraDeviceClient的本地介面(BpCameraDeviceUser),ICameraDeviceUser UML類圖如下:
frameworks/av/camera/aidl/android/hardware/camera2/ICameraDeviceUser.aidl

圖2-2-4 ICameraDeviceUser UML類圖

Step5,最后將CameraDeviceClient物件保存到CameraDeviceImpl中進行管理,
Step2 new android.hardware.camera2.impl.CameraDeviceImpl
檔案:"frameworks/base/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java"
將輸入引數保存到該類的成員變數,

Step3 getCameraService
檔案: "frameworks/base/core/java/android/hardware/camera2/CameraManager.java"
注意這個CameraManagerGlobal類,是個static單例類,用來保存對CameraService的連接,

    /**
     * A per-process global camera manager instance, to retain a connection to the camera service,
     * and to distribute camera availability notices to API-registered callbacks
     */
    private static final class CameraManagerGlobal extends ICameraServiceListener.Stub
            implements IBinder.DeathRecipient
    {                                   
        private static final String TAG = "CameraManagerGlobal";
        ...                                                                                                                                                  
        // Singleton instance
        private static final CameraManagerGlobal gCameraManager =
            new CameraManagerGlobal();
        ...
        
        // Singleton, don't allow construction
        private CameraManagerGlobal() {
        }

        public static CameraManagerGlobal get() {
            return gCameraManager;
        }
        
        /**
         * Return a best-effort ICameraService.
         *
         * <p>This will be null if the camera service is not currently available. If the camera
         * service has died since the last use of the camera service, will try to reconnect to the
         * service.</p>
         */
        public ICameraService getCameraService() {
            synchronized(mLock) {
                //3.1
                connectCameraServiceLocked();
                if (mCameraService == null && !sCameraServiceDisabled) {
                    Log.e(TAG, "Camera service is unavailable");
                }
                return mCameraService;
            }
        }
    }

ICameraServiceListener.aidl:./frameworks/av/camera/aidl/android/hardware/ICameraServiceListener.aidl
3.1 connectCameraServiceLocked

        /**
         * Connect to the camera service if it's available, and set up listeners.
         * If the service is already connected, do nothing.
         *
         * <p>Sets mCameraService to a valid pointer or null if the connection does not succeed.</p>
         */
        private void connectCameraServiceLocked() {
            // Only reconnect if necessary
            if (mCameraService != null || sCameraServiceDisabled) return;
            Log.i(TAG, "Connecting to camera service");
            // 3.1.1
            IBinder cameraServiceBinder = ServiceManager.getService(CAMERA_SERVICE_BINDER_NAME);
            ...
            // 3.1.2
            ICameraService cameraService = ICameraService.Stub.asInterface(cameraServiceBinder);
            try {
                // 注冊回呼監聽,CameraService(下層)可以通知CameraManagerGlobal(上層)狀態變化
                // 3.1.3
                CameraStatus[] cameraStatuses = cameraService.addListener(this);
                for (CameraStatus c : cameraStatuses) {
                    onStatusChangedLocked(c.status, c.cameraId);
                }
                mCameraService = cameraService;
            }
        }

首先從ServiceManager里查找CameraService服務,CAMERA_SERVICE_BINDER_NAME為字串"media.camera",找到CameraService后,將CameraManagerGlobal類的this指標設定為CameraService的回呼監聽,
3.1.3 addListener
檔案:frameworks/av/services/camera/libcameraservice/CameraService.cpp
來看下整體架構圖中的ICameraServiceListener.aidl回呼介面怎么注冊的:

// 注冊回呼
private void connectCameraServiceLocked() {
    ...
    // 3.1.3
    CameraStatus[] cameraStatuses = cameraService.addListener(this);
    for (CameraStatus c : cameraStatuses) {
        onStatusChangedLocked(c.status, c.cameraId);
    }
}

// 呼叫服務
Status CameraService::addListener(const sp<ICameraServiceListener>& listener,
        std::vector<hardware::CameraStatus> *cameraStatuses) {
    for (size_t i = 0; i < mTorchStatusMap.size(); i++ ) {
        String16 id = String16(mTorchStatusMap.keyAt(i).string());
        // 跨行程回呼監聽函式
        // 3.1.4
        listener->onTorchStatusChanged(mapToInterface(mTorchStatusMap.valueAt(i)), id); 
    }
}

通過以上注冊回呼和呼叫服務的流程,實作將java服務作為監聽物件注冊到C++層服務,C++層服務跨行程回呼java層服務,下圖是ICameraServiceListener UML類圖:

圖2-2-5 ICameraServiceListener UML類圖

Step4 connectDevice
檔案:"./frameworks/av/services/camera/libcameraservice/CameraService.cpp"

Status CameraService::connectDevice(
        const sp<hardware::camera2::ICameraDeviceCallbacks>& cameraCb,
        const String16& cameraId,
        const String16& clientPackageName,
        int clientUid,
        /*out*/
        sp<hardware::camera2::ICameraDeviceUser>* device) {
    ATRACE_CALL();
    Status ret = Status::ok();
    String8 id = String8(cameraId);
    sp<CameraDeviceClient> client = nullptr;
    // 4.1
    ret = connectHelper<hardware::camera2::ICameraDeviceCallbacks,CameraDeviceClient>(cameraCb, id,
            /*api1CameraId*/-1,
            CAMERA_HAL_API_VERSION_UNSPECIFIED, clientPackageName,
            clientUid, USE_CALLING_PID, API_2,
            /*legacyMode*/ false, /*shimUpdateOnly*/ false,
            /*out*/client);
    if(!ret.isOk()) {
        logRejected(id, getCallingPid(), String8(clientPackageName),
                ret.toString8());
        return ret;
    }
    *device = client;
    return ret;
}

這個方法實作在CameraService類中,client是最侄訓傳的CameraDeviceClient物件,

template<class CALLBACK, class CLIENT>
Status CameraService::connectHelper(const sp<CALLBACK>& cameraCb, const String8& cameraId,
        int halVersion, const String16& clientPackageName, int clientUid, int clientPid,
        apiLevel effectiveApiLevel, bool legacyMode, bool shimUpdateOnly,
        /*out*/sp<CLIENT>& device) {
    binder::Status ret = binder::Status::ok();
    String8 clientName8(clientPackageName);
    ...
        sp<BasicClient> tmp = nullptr;
        // 4.2
        if(!(ret = makeClient(this, cameraCb, clientPackageName, cameraId, facing, clientPid,
                clientUid, getpid(), legacyMode, halVersion, deviceVersion, effectiveApiLevel,
                /*out*/&tmp)).isOk()) {
            return ret;
        }
        client = static_cast<CLIENT*>(tmp.get());
        LOG_ALWAYS_FATAL_IF(client.get() == nullptr, "%s: CameraService in invalid state",
                __FUNCTION__);
        // 4.3
        err = client->initialize(mCameraProviderManager);

    ...
    // Important: release the mutex here so the client can call back into the service from its
    // destructor (can be at the end of the call)
    device = client;
    return ret;
}

CALLBACK為ICameraDeviceCallbacks,CLIENT為CameraDeviceClient,先通過makeClient生成client(CameraDeviceClient物件),再呼叫CameraDeviceClient的initialize函式,
4.2 makeClient

Status CameraService::makeClient(const sp<CameraService>& cameraService,
        const sp<IInterface>& cameraCb, const String16& packageName, const String8& cameraId,
        int api1CameraId, int facing, int clientPid, uid_t clientUid, int servicePid,
        bool legacyMode, int halVersion, int deviceVersion, apiLevel effectiveApiLevel,
        /*out*/sp<BasicClient>* client) {
    if (halVersion < 0 || halVersion == deviceVersion) {
        switch(deviceVersion) {
          ...
          case CAMERA_DEVICE_API_VERSION_3_4:
            if (effectiveApiLevel == API_1) { // Camera1 API route
                sp<ICameraClient> tmp = static_cast<ICameraClient*>(cameraCb.get());
                *client = new Camera2Client(cameraService, tmp, packageName,
                        cameraId, api1CameraId,
                        facing, clientPid, clientUid,
                        servicePid, legacyMode);
            } else { // Camera2 API route
                sp<hardware::camera2::ICameraDeviceCallbacks> tmp =
                        static_cast<hardware::camera2::ICameraDeviceCallbacks*>(cameraCb.get());
                //4.2.1
                *client = new CameraDeviceClient(cameraService, tmp, packageName, cameraId,
                        facing, clientPid, clientUid, servicePid);
            }
            break;
          default:
            // Should not be reachable
            ALOGE("Unknown camera device HAL version: %d", deviceVersion);
            return STATUS_ERROR_FMT(ERROR_INVALID_OPERATION,
                    "Camera device \"%s\" has unknown HAL version %d",
                    cameraId.string(), deviceVersion);
        }
    } else {
        ...
    }
    ...
}

makeClient主要是根據 API 版本以及 HAL 版本來實體化CameraDeviceClient,最后在Step5,這一client回傳到openCameraDeviceUserAsync中保存起來,
Step5 setRemoteDevice
檔案:"./frameworks/base/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java"

    /**
     * Set remote device, which triggers initial onOpened/onUnconfigured callbacks
     *
     * <p>This function may post onDisconnected and throw CAMERA_DISCONNECTED if remoteDevice dies
     * during setup.</p>
     *
     */
    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;
            // 5.1
            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);
        }
    }

將打開Camera獲取的ICameraDeviceUser物件(即Framework層的CameraDeviceClient物件)封裝到一個ICameraDeviceUserWrapper類的實體中,并保存為mRemoteDevice,注意,此處觸發onOpened與onUnconfigured這兩個回呼,每個回呼都是通過mDeviceHandler啟用一個新執行緒來呼叫的,

小結:
通過以上流程,相機應用獲得了CameraDeviceImpl物件,而CameraDeviceImpl保存了ICameraDeviceUserWrapper物件,ICameraDeviceUserWrapper保存BpCameraDeviceUser物件,BpCameraDeviceUser具有aidl跨行程的能力,這樣應用就可以和CameraService端的CameraDeviceClient進行通信了,
Java層要想與C++層的CameraService層進行通信,是通過aidl進行的,主要包括ICameraService.aidl以及ICameraDeviceUser.aidl兩個介面來實作,其會在Java層維護一個CameraDeviceImpl類(即BpCameraDeviceUser類),

2.3 camera server行程<-->hal(camera provider)行程

camera server行程與camera provider行程,它們之間通過HIDL進行通信,CameraService會尋找現存的ProviderService,將其加入到內部的 CameraProviderManager中進行管理,相關操作都是通過遠端呼叫進行的;而CameraProvider,它在初始化時(initialize)就已經連接到libhardware的Camera HAL實作層,并用CameraModule來進行管理,(圖2-1-1 camera server行程與hal(camera provider)行程啟動架構圖)
這一部分的主要呼叫邏輯如下圖:

圖2-3-1 camera server行程<-->hal(camera provider)行程架構圖

下圖是camera server行程<-->hal(camera provider)行程 UML順序圖:

圖2-3-2 camera server行程<-->hal(camera provider)行程 UML順序圖

2.2節(4.2.1)講到實體化了一個CameraDeviceClient物件,我們從它的建構式開始分析:
Step1 new CameraDeviceClient(2.2節(4.2.1))
檔案:"./frameworks/av/services/camera/libcameraservice/api2/CameraDeviceClient.cpp"

CameraDeviceClient::CameraDeviceClient(const sp<CameraService>& cameraService,
        const sp<hardware::camera2::ICameraDeviceCallbacks>& remoteCallback,
        const String16& clientPackageName,
        const String8& cameraId,
        int cameraFacing,
        int clientPid,
        uid_t clientUid,
        int servicePid) :
    // 1.1
    Camera2ClientBase(cameraService, remoteCallback, clientPackageName,
                cameraId, /*API1 camera ID*/ -1,
                cameraFacing, clientPid, clientUid, servicePid),
    mInputStream(),
    mStreamingRequestId(REQUEST_ID_NONE),
    mRequestIdCounter(0) {
    ATRACE_CALL();
    ALOGI("CameraDeviceClient %s: Opened", cameraId.string());
}

1.1 Camera2ClientBase
檔案:"frameworks/av/services/camera/libcameraservice/common/Camera2ClientBase.cpp"

template <typename TClientBase>
Camera2ClientBase<TClientBase>::Camera2ClientBase(
        const sp<CameraService>& cameraService,
        const sp<TCamCallbacks>& remoteCallback,
        const String16& clientPackageName,
        const String8& cameraId,
        int api1CameraId,
        int cameraFacing,
        int clientPid,
        uid_t clientUid,
        int servicePid):
        TClientBase(cameraService, remoteCallback, clientPackageName,
                cameraId, api1CameraId, cameraFacing, clientPid, clientUid, servicePid),
        mSharedCameraCallbacks(remoteCallback),
        mDeviceVersion(cameraService->getDeviceVersion(TClientBase::mCameraIdStr)),
        mDeviceActive(false), mApi1CameraId(api1CameraId)
{
    ALOGI("Camera %s: Opened. Client: %s (PID %d, UID %d)", cameraId.string(),
            String8(clientPackageName).string(), clientPid, clientUid);
    mInitialClientPid = clientPid;
    // 1.2
    mDevice = new Camera3Device(cameraId);
    LOG_ALWAYS_FATAL_IF(mDevice == 0, "Device should never be NULL here.");
}

TClientBase是CameraDeviceClientBase類,從ICameraDeviceUser UML類圖中CameraDeviceClient的繼承關系可以看出,TCamCallbacks是ICameraDeviceCallbacks類,創建Camera3Device實體,
1.2 new Camera3Device
檔案:"frameworks/av/services/camera/libcameraservice/device3/Camera3Device.cpp"

Camera3Device::Camera3Device(const String8 &id):
        mId(id),
        mOperatingMode(NO_MODE),
        mIsConstrainedHighSpeedConfiguration(false),
        mStatus(STATUS_UNINITIALIZED),
        mStatusWaiters(0),
        mUsePartialResult(false),
        mNumPartialResults(1),
        mTimestampOffset(0),
        mNextResultFrameNumber(0),
        mNextReprocessResultFrameNumber(0),
        mNextShutterFrameNumber(0),
        mNextReprocessShutterFrameNumber(0),
        mListener(NULL),
        mVendorTagId(CAMERA_METADATA_INVALID_VENDOR_ID),
        mLastTemplateId(-1)
{
    ATRACE_CALL();
    camera3_callback_ops::notify = &sNotify;
    camera3_callback_ops::process_capture_result = &sProcessCaptureResult;
    ALOGV("%s: Created device for camera %s", __FUNCTION__, mId.string());
}

設定兩個回呼,
Step2 initialize(2.2節(4.3))
檔案:"./frameworks/av/services/camera/libcameraservice/api2/CameraDeviceClient.cpp"

status_t CameraDeviceClient::initialize(sp<CameraProviderManager> manager,
        const String8& monitorTags) {
    // 2.1
    return initializeImpl(manager, monitorTags);
}

template<typename TProviderPtr>
status_t CameraDeviceClient::initializeImpl(TProviderPtr providerPtr, const String8& monitorTags) {
    ATRACE_CALL();
    status_t res;
    // 2.2
    res = Camera2ClientBase::initialize(providerPtr, monitorTags);
    if (res != OK) {
        return res;
    }
    ...
    
    return OK;
}

TProviderPtr是指CameraProviderManager類,呼叫父類初始化介面,
2.2 Camera2ClientBase::initialize
檔案:"frameworks/av/services/camera/libcameraservice/common/Camera2ClientBase.cpp"

template <typename TClientBase>
status_t Camera2ClientBase<TClientBase>::initialize(sp<CameraProviderManager> manager,
        const String8& monitorTags) {
    // 2.3
    return initializeImpl(manager, monitorTags);
}

template <typename TClientBase>
template <typename TProviderPtr>
status_t Camera2ClientBase<TClientBase>::initializeImpl(TProviderPtr providerPtr,
        const String8& monitorTags) {
    ATRACE_CALL();
    ALOGV("%s: Initializing client for camera %s", __FUNCTION__,
          TClientBase::mCameraIdStr.string());
    status_t res;

    // Verify ops permissions
    res = TClientBase::startCameraOps();
    if (res != OK) {
        return res;
    }

    if (mDevice == NULL) {
        ALOGE("%s: Camera %s: No device connected",
                __FUNCTION__, TClientBase::mCameraIdStr.string());
        return NO_INIT;
    }
    // Step 3
    res = mDevice->initialize(providerPtr, monitorTags);
    if (res != OK) {
        ALOGE("%s: Camera %s: unable to initialize device: %s (%d)",
                __FUNCTION__, TClientBase::mCameraIdStr.string(), strerror(-res), res);
        return res;
    }

    wp<CameraDeviceBase::NotificationListener> weakThis(this);
    res = mDevice->setNotifyCallback(weakThis);

    return OK;
}

注意此處,TClientBase 對應 CameraDeviceClientBase,而 TProviderPtr 對應的是 CameraProviderManager,mDevice是Camera3Device物件,
呼叫TClientBase(CameraDeviceClientBase)的 startCameraOps 方法,檢查 ops 的權限;初始化Camera3Device;為Camera3Device設定Notify回呼,
Step3 mDevice->initialize
檔案:"frameworks/av/services/camera/libcameraservice/device3/Camera3Device.cpp"

status_t Camera3Device::initialize(sp<CameraProviderManager> manager, const String8& monitorTags) {
    ATRACE_CALL();
    Mutex::Autolock il(mInterfaceLock);
    Mutex::Autolock l(mLock);

    ALOGV("%s: Initializing HIDL device for camera %s", __FUNCTION__, mId.string());
    if (mStatus != STATUS_UNINITIALIZED) {
        CLOGE("Already initialized!");
        return INVALID_OPERATION;
    }
    if (manager == nullptr) return INVALID_OPERATION;

    sp<ICameraDeviceSession> session;
    ATRACE_BEGIN("CameraHal::openSession");
    // Step 4
    status_t res = manager->openSession(mId.string(), this,
            /*out*/ &session);
    ATRACE_END();
    if (res != OK) {
        SET_ERR_L("Could not open camera session: %s (%d)", strerror(-res), res);
        return res;
    }
    ...
    mInterface = new HalInterface(session, queue);
    std::string providerType;
    mVendorTagId = manager->getProviderTagIdLocked(mId.string());
    mTagMonitor.initialize(mVendorTagId);
    if (!monitorTags.isEmpty()) {
        mTagMonitor.parseTagsToMonitor(String8(monitorTags));
    }

    return initializeCommonLocked();
}

呼叫了CameraProviderManager的openSession方法,打開了一個遠端的Session,
Step4 openSession
檔案:"frameworks/av/services/camera/libcameraservice/common/CameraProviderManager.cpp"

status_t CameraProviderManager::openSession(const std::string &id,
        const sp<hardware::camera::device::V3_2::ICameraDeviceCallback>& callback,
        /*out*/
        sp<hardware::camera::device::V3_2::ICameraDeviceSession> *session) {
    std::lock_guard<std::mutex> lock(mInterfaceMutex);
    // 根據id和version找到對應的CameraDevice3
    // 4.1
    auto deviceInfo = findDeviceInfoLocked(id,
            /*minVersion*/ {3,0}, /*maxVersion*/ {4,0});
    if (deviceInfo == nullptr) return NAME_NOT_FOUND;
    auto *deviceInfo3 = static_cast<ProviderInfo::DeviceInfo3*>(deviceInfo);
    Status status;
    hardware::Return<void> ret;
    // 其中mInterface是指Hal層的CameraDevice類, cameraSession等于CameraDevice3SessionImpl
    // Step 5
    ret = deviceInfo3->mInterface->open(callback, [&status, &session]
            (Status s, const sp<device::V3_2::ICameraDeviceSession>& cameraSession) {
                status = s;
                if (status == Status::OK) {
                    *session = cameraSession;
                }
            });
    if (!ret.isOk()) {
        ALOGE("%s: Transaction error opening a session for camera device %s: %s",
                __FUNCTION__, id.c_str(), ret.description().c_str());
        return DEAD_OBJECT;
    }
    return mapToStatusT(status);
}

首先呼叫 findDeviceInfoLocked,獲取 HAL3 相關的 DeviceInfo3,這個資訊在CameraService服務啟動與初始化的時候就已經創建出來并保存下來了,
然后通過hidl遠端呼叫CameraDevice的open 方法(todo hidl遠端呼叫具體怎么行程間通信的?),創建CameraDeviceSession實體并通過session回傳,DeviceInfo3這個類的mInterface成員型別是ICameraDevice,通過它可以呼叫遠端(Hal層)CameraDevice中的方法,
檔案:"frameworks/av/services/camera/libcameraservice/common/CameraProviderManager.h"

        // HALv3-specific camera fields, including the actual device interface
        struct DeviceInfo3 : public DeviceInfo {
            typedef hardware::camera::device::V3_2::ICameraDevice InterfaceT;
            const sp<InterfaceT> mInterface;

            virtual status_t setTorchMode(bool enabled) override;
            virtual status_t getCameraInfo(hardware::CameraInfo *info) const override;
            virtual bool isAPI1Compatible() const override;
            virtual status_t dumpState(int fd) const override;
            virtual status_t getCameraCharacteristics(
                    CameraMetadata *characteristics) const override;

            DeviceInfo3(const std::string& name, const metadata_vendor_id_t tagId,
                    const std::string &id, uint16_t minorVersion,
                    const hardware::camera::common::V1_0::CameraResourceCost& resourceCost,
                    sp<InterfaceT> interface);
            virtual ~DeviceInfo3();
        private:
            CameraMetadata mCameraCharacteristics;
        };

Step5 open

檔案:"./hardware/interfaces/camera/device/3.2/default/CameraDevice.cpp"

Return<void> CameraDevice::open(const sp<ICameraDeviceCallback>& callback, open_cb _hidl_cb)  {
    Status status = initStatus();
    sp<CameraDeviceSession> session = nullptr;
    ...
        /** Open HAL device */
        status_t res;
        camera3_device_t *device;

        ATRACE_BEGIN("camera3->open");
        // 5.1
        res = mModule->open(mCameraId.c_str(),
                reinterpret_cast<hw_device_t**>(&device));
        ATRACE_END();
        ...
        struct camera_info info;
        res = mModule->getCameraInfo(mCameraIdInt, &info);
        if (res != OK) {
            ALOGE("%s: Could not open camera: getCameraInfo failed", __FUNCTION__);
            device->common.close(&device->common);
            mLock.unlock();
            _hidl_cb(Status::ILLEGAL_ARGUMENT, nullptr);
            return Void();
        }
        // 5.2
        session = createSession(
                device, info.static_camera_characteristics, callback);
        if (session == nullptr) {
            ALOGE("%s: camera device session allocation failed", __FUNCTION__);
            mLock.unlock();
            _hidl_cb(Status::INTERNAL_ERROR, nullptr);
            return Void();
        }
        ...
        mSession = session;
    ...
    return Void();
}

sp<CameraDeviceSession> CameraDevice::createSession(camera3_device_t* device,
        const camera_metadata_t* deviceInfo,
        const sp<ICameraDeviceCallback>& callback) {
    // 5.3
    return new CameraDeviceSession(device, deviceInfo, callback);
}

CameraDevice的實體是在初始化CameraProvider時初始化的(todo),這個函式主要做兩件事,一件事是通過mModule呼叫Camera HAL介面的open介面,mModule(CameraModule)是對Camera HAL的一層封裝;另一件事是創建session并讓mSession保存創建的session,
creatSession中直接創建了一個 CameraDeviceSession實體,
ICameraDevice.hal:./hardware/interfaces/camera/device/3.2/ICameraDevice.hal
ICameraDevice UML類圖如下:

圖2-3-3 ICameraDevice UML類圖

2.4 camera hal分析

在 HAL3 中,Camera HAL的介面轉化層以及流決議層由QCamera3HardwareInterface負責,而介面層與實作層是在mm_camera_interface.c與mm_camera.c 中,下圖展示了Camera HAL初始化的主要呼叫流程:

圖2-4-1 camera hal分析架構圖

下圖是camera hal分析UML順序圖:

圖2-4-2 camera hal分析UML順序圖

Step1 open(2.3節(5.1))
檔案:"./hardware/interfaces/camera/common/1.0/default/CameraModule.cpp"
2.3節(5.1)講到呼叫了mModule->open,即CameraModule::open(),接著往下看:

int CameraModule::open(const char* id, struct hw_device_t** device) {
    int res;
    ATRACE_BEGIN("camera_module->open");
    // Step 2
    res = filterOpenErrorCode(mModule->common.methods->open(&mModule->common, id, device));
    ATRACE_END();
    return res;
}

注意,mModule是camera_module型別,mModule是在建構式里賦值的,而CameraModule實體化是在CameraProvider實體化時(見2.1.1節(6.1.5)),
open 是一個函式指標,它指向的是QCamera2Factory的camera_device_open方法,分析如下:
先看下這些struct型別定義(camera_module, hw_module_t, hw_module_methods_t):
檔案:"hardware/libhardware/include/hardware/camera_common.h"

typedef struct camera_module {
    /**
     * Common methods of the camera module.  This *must* be the first member of
     * camera_module as users of this structure will cast a hw_module_t to
     * camera_module pointer in contexts where it's known the hw_module_t
     * references a camera_module.
     *
     * The return values for common.methods->open for camera_module are:
     *
     * 0:           On a successful open of the camera device.
     *
     * -ENODEV:     The camera device cannot be opened due to an internal
     *              error.
     *
     * -EINVAL:     The input arguments are invalid, i.e. the id is invalid,
     *              and/or the module is invalid.
     *
     * -EBUSY:      The camera device was already opened for this camera id
     *              (by using this method or open_legacy),
     *              regardless of the device HAL version it was opened as.
     *
     * -EUSERS:     The maximal number of camera devices that can be
     *              opened concurrently were opened already, either by
     *              this method or the open_legacy method.
     *
     * All other return values from common.methods->open will be treated as
     * -ENODEV.
     */
    hw_module_t common;
    ...
}

檔案:"hardware/libhardware/include/hardware/hardware.h"

typedef struct hw_module_t {
    /** tag must be initialized to HARDWARE_MODULE_TAG */
    uint32_t tag;
    ...
    /** Identifier of module */
    const char *id;
    /** Name of this module */
    const char *name;
    /** Author/owner/implementor of the module */
    const char *author;
    /** Modules methods */
    struct hw_module_methods_t* methods;
    ...
}

typedef struct hw_module_methods_t {
    /** Open a specific device */
    int (*open)(const struct hw_module_t* module, const char* id,
            struct hw_device_t** device);
} hw_module_methods_t;

hw_module_t中的成員是在QCamera2Hal.cpp賦值的,每個廠商不一樣,以qcom msm8998為例:
檔案:"hardware/qcom/camera/msm8998/QCamera2/QCamera2Hal.cpp"

static hw_module_t camera_common = {
    .tag                    = HARDWARE_MODULE_TAG,
    .module_api_version     = CAMERA_MODULE_API_VERSION_2_4,
    .hal_api_version        = HARDWARE_HAL_API_VERSION,
    .id                     = CAMERA_HARDWARE_MODULE_ID,
    .name                   = "QCamera Module",
    .author                 = "Qualcomm Innovation Center Inc",
    .methods                = &qcamera::QCamera2Factory::mModuleMethods,
    .dso                    = NULL,
    .reserved               = {0}
};

檔案:"./hardware/qcom/camera/msm8998/QCamera2/QCamera2Factory.cpp"

struct hw_module_methods_t QCamera2Factory::mModuleMethods = {
    .open = QCamera2Factory::camera_device_open,
};

注意,mModuleMethods是個static類成員變數,
可以得出結論:open指向的是 QCamera2Factory的camera_device_open方法,
Step2 camera_device_open
檔案:"./hardware/qcom/camera/msm8998/QCamera2/QCamera2Factory.cpp"

/*===========================================================================
* FUNCTION   : camera_device_open
*
* DESCRIPTION: static function to open a camera device by its ID
*
* PARAMETERS :
*   @camera_id : camera ID
*   @hw_device : ptr to struct storing camera hardware device info
*
* RETURN     : int32_t type of status
*              NO_ERROR  -- success
*              none-zero failure code
*==========================================================================*/
int QCamera2Factory::camera_device_open(
    const struct hw_module_t *module, const char *id,
    struct hw_device_t **hw_device)
{
    int rc = NO_ERROR;
    if (module != &HAL_MODULE_INFO_SYM.common) {
        LOGE("Invalid module. Trying to open %p, expect %p",
            module, &HAL_MODULE_INFO_SYM.common);
        return INVALID_OPERATION;
    }
    if (!id) {
        LOGE("Invalid camera id");
        return BAD_VALUE;
    }
#ifdef QCAMERA_HAL1_SUPPORT
    if(gQCameraMuxer)
        rc =  gQCameraMuxer->camera_device_open(module, id, hw_device);
    else
#endif
        // 2.1
        rc = gQCamera2Factory->cameraDeviceOpen(atoi(id), hw_device);
    return rc;
}

/*===========================================================================
* FUNCTION   : cameraDeviceOpen
*
* DESCRIPTION: open a camera device with its ID
*
* PARAMETERS :
*   @camera_id : camera ID
*   @hw_device : ptr to struct storing camera hardware device info
*
* RETURN     : int32_t type of status
*              NO_ERROR  -- success
*              none-zero failure code
*==========================================================================*/
int QCamera2Factory::cameraDeviceOpen(int camera_id,
                    struct hw_device_t **hw_device)
{
    int rc = NO_ERROR;
    if (camera_id < 0 || camera_id >= mNumOfCameras)
        return -ENODEV;

    if ( NULL == mHalDescriptors ) {
        LOGE("Hal descriptor table is not initialized!");
        return NO_INIT;
    }

    LOGI("Open camera id %d API version %d",
            camera_id, mHalDescriptors[camera_id].device_version);

    if ( mHalDescriptors[camera_id].device_version == CAMERA_DEVICE_API_VERSION_3_0 ) {
        CAMSCOPE_INIT(CAMSCOPE_SECTION_HAL);
        // 2.2
        QCamera3HardwareInterface *hw = new QCamera3HardwareInterface(mHalDescriptors[camera_id].cameraId,
                mCallbacks);
        if (!hw) {
            LOGE("Allocation of hardware interface failed");
            return NO_MEMORY;
        }
        // Step3
        rc = hw->openCamera(hw_device);
        if (rc != 0) {
            delete hw;
        }
    }
    ...
}

HAL3呼叫cameraDeviceOpen函式,創建QCamera3HardwareInterface實體,呼叫openCamera介面,
Step3 openCamera
檔案:"hardware/qcom/camera/msm8998/QCamera2/HAL3/QCamera3HWI.cpp"

/*===========================================================================
* FUNCTION   : openCamera
*
* DESCRIPTION: open camera
*
* PARAMETERS :
*   @hw_device  : double ptr for camera device struct
*
* RETURN     : int32_t type of status
*              NO_ERROR  -- success
*              none-zero failure code
*==========================================================================*/
int QCamera3HardwareInterface::openCamera(struct hw_device_t **hw_device)
{
    int rc = 0;
    if (mState != CLOSED) {
        *hw_device = NULL;
        return PERMISSION_DENIED;
    }
    ...
    // 3.1
    rc = openCamera();
    if (rc == 0) {
        *hw_device = &mCameraDevice.common;
    } else {
        *hw_device = NULL;
        ...
    }
    ...
}

/*===========================================================================
* FUNCTION   : openCamera
*
* DESCRIPTION: open camera
*
* PARAMETERS : none
*
* RETURN     : int32_t type of status
*              NO_ERROR  -- success
*              none-zero failure code
*==========================================================================*/
int QCamera3HardwareInterface::openCamera()
{
    int rc = 0;
    char value[PROPERTY_VALUE_MAX];
    // Step 4
    rc = camera_open((uint8_t)mCameraId, &mCameraHandle);
    if (rc) {
        LOGE("camera_open failed. rc = %d, mCameraHandle = %p", rc, mCameraHandle);
        return rc;
    }
    if (!mCameraHandle) {
        LOGE("camera_open failed. mCameraHandle = %p", mCameraHandle);
        return -ENODEV;
    }
    rc = mCameraHandle->ops->register_event_notify(mCameraHandle->camera_handle,
            camEvtHandle, (void *)this);
    ...
}

openCamera的作業是從介面轉化層進入到介面層,openCamera成功后,將mCameraDevice.common通過雙重指標 hw_device 回傳,介面層是呼叫camera_open介面,并通過mCameraHandle注冊notify回呼,
Step4 camera_open
檔案:"./hardware/qcom/camera/msm8998/QCamera2/stack/mm-camera-interface/src/mm_camera_interface.c"

/*===========================================================================
* FUNCTION   : camera_open
*
* DESCRIPTION: open a camera by camera index
*
* PARAMETERS :
*   @camera_idx  : camera index. should within range of 0 to num_of_cameras
*   @camera_vtbl : ptr to a virtual table containing camera handle and operation table.
*
* RETURN     : int32_t type of status
*              0  -- success
*              non-zero error code -- failure
*==========================================================================*/
int32_t camera_open(uint8_t camera_idx, mm_camera_vtbl_t **camera_vtbl)
{
    int32_t rc = 0;
    mm_camera_obj_t *cam_obj = NULL;
    uint32_t cam_idx = camera_idx;
    uint32_t aux_idx = 0;
    uint8_t is_multi_camera = 0;
    ...
    // Step 5
    rc = mm_camera_open(cam_obj);
    if (rc != 0) {
        LOGE("mm_camera_open err = %d", rc);
        pthread_mutex_destroy(&cam_obj->cam_lock);
        pthread_mutex_lock(&g_intf_lock);
        g_cam_ctrl.cam_obj[cam_idx] = NULL;
        free(cam_obj);
        cam_obj = NULL;
        pthread_mutex_unlock(&g_intf_lock);
        *camera_vtbl = NULL;
        return rc;
    }
    ...
}

進入介面層,呼叫mm_camera_open,
Step5 mm_camera_open
檔案:"hardware/qcom/camera/msm8998/QCamera2/stack/mm-camera-interface/src/mm_camera.c"

/*===========================================================================
* FUNCTION   : mm_camera_open
*
* DESCRIPTION: open a camera
*
* PARAMETERS :
*   @my_obj   : ptr to a camera object
*
* RETURN     : int32_t type of status
*              0  -- success
*              -1 -- failure
*==========================================================================*/
int32_t mm_camera_open(mm_camera_obj_t *my_obj)
{
    char dev_name[MM_CAMERA_DEV_NAME_LEN];
    int32_t rc = 0;
    int8_t n_try=MM_CAMERA_DEV_OPEN_TRIES;
    uint8_t sleep_msec=MM_CAMERA_DEV_OPEN_RETRY_SLEEP;
    int cam_idx = 0;
    const char *dev_name_value = NULL;
    int l_errno = 0;

    LOGD("begin\n");

    if (NULL == my_obj) {
        goto on_error;
    }

    dev_name_value = mm_camera_util_get_dev_name_by_num(my_obj->my_num,
            my_obj->my_hdl);
    if (NULL == dev_name_value) {
        goto on_error;
    }
    snprintf(dev_name, sizeof(dev_name), "/dev/%s",
             dev_name_value);
    sscanf(dev_name, "/dev/video%d", &cam_idx);
    LOGD("dev name = %s, cam_idx = %d", dev_name, cam_idx);

    do{
        n_try--;
        errno = 0;
        my_obj->ctrl_fd = open(dev_name, O_RDWR | O_NONBLOCK);
        l_errno = errno;
        LOGD("ctrl_fd = %d, errno == %d", my_obj->ctrl_fd, l_errno);
        if((my_obj->ctrl_fd >= 0) || (errno != EIO && errno != ETIMEDOUT) || (n_try <= 0 )) {
            break;
        }
        LOGE("Failed with %s error, retrying after %d milli-seconds",
              strerror(errno), sleep_msec);
        usleep(sleep_msec * 1000U);
    }while (n_try > 0);
    ...
    LOGD("Launch evt Thread in Cam Open");
    snprintf(my_obj->evt_thread.threadName, THREAD_NAME_SIZE, "CAM_Dispatch");
    mm_camera_cmd_thread_launch(&my_obj->evt_thread,
                                mm_camera_dispatch_app_event,
                                (void *)my_obj);

    /* launch event poll thread
     * we will add evt fd into event poll thread upon user first register for evt */
    LOGD("Launch evt Poll Thread in Cam Open");
    snprintf(my_obj->evt_poll_thread.threadName, THREAD_NAME_SIZE, "CAM_evntPoll");
    mm_camera_poll_thread_launch(&my_obj->evt_poll_thread,
                                 MM_CAMERA_POLL_TYPE_EVT);
    mm_camera_evt_sub(my_obj, TRUE);
    ...
}

打開camera設備檔案open("/dev/video%d",...),并起事件執行緒,
Step6 new CameraDeviceSession((2.3節(5.3)))
檔案:"./hardware/interfaces/camera/device/3.2/default/CameraDeviceSession.cpp"
2.3節分析過,呼叫CameraModule的open介面后會創建CameraDeviceSession實體,繼續往下看,這里會呼叫QCamera3HWI的初始化方法 initialize,

CameraDeviceSession::CameraDeviceSession(
    camera3_device_t* device,
    const camera_metadata_t* deviceInfo,
    const sp<ICameraDeviceCallback>& callback) :
        camera3_callback_ops({&sProcessCaptureResult, &sNotify}),
        mDevice(device),
        mDeviceVersion(device->common.version),
        mIsAELockAvailable(false),
        mDerivePostRawSensKey(false),
        mNumPartialResults(1),
        mResultBatcher(callback) {
    mDeviceInfo = deviceInfo;
    camera_metadata_entry partialResultsCount =
            mDeviceInfo.find(ANDROID_REQUEST_PARTIAL_RESULT_COUNT);
    if (partialResultsCount.count > 0) {
        mNumPartialResults = partialResultsCount.data.i32[0];
    }
    mResultBatcher.setNumPartialResults(mNumPartialResults);                                                                                                                                                                                 
    camera_metadata_entry aeLockAvailableEntry = mDeviceInfo.find(
            ANDROID_CONTROL_AE_LOCK_AVAILABLE);
        ...
    // 6.1
    mInitFail = initialize();
}

bool CameraDeviceSession::initialize() {
    /** Initialize device with callback functions */
    ATRACE_BEGIN("camera3->initialize");
    // Step7
    status_t res = mDevice->ops->initialize(mDevice, this);
    ATRACE_END();

    if (res != OK) {
        ALOGE("%s: Unable to initialize HAL device: %s (%d)",
                __FUNCTION__, strerror(-res), res);
        mDevice->common.close(&mDevice->common);
        mClosed = true;
        return true;
    }
    ...
}

檔案:"hardware/libhardware/include/hardware/camera3.h"

/**********************************************************************
*
* Camera device definition
*
*/
typedef struct camera3_device {
    /**
     * common.version must equal CAMERA_DEVICE_API_VERSION_3_0 to identify this
     * device as implementing version 3.0 of the camera device HAL.
     *
     * Performance requirements:
     *
     * Camera open (common.module->common.methods->open) should return in 200ms, and must return
     * in 500ms.
     * Camera close (common.close) should return in 200ms, and must return in 500ms.
     *
     */
    hw_device_t common;
    camera3_device_ops_t *ops;
    void *priv;
} camera3_device_t;

/**********************************************************************
*
* Camera device operations
*
*/
typedef struct camera3_device_ops {
    /**
     * initialize:
     *
     * One-time initialization to pass framework callback function pointers to
     * the HAL. Will be called once after a successful open() call, before any
     * other functions are called on the camera3_device_ops structure.
     *
     * Performance requirements:
     *
     * This should be a non-blocking call. The HAL should return from this call
     * in 5ms, and must return from this call in 10ms.
     *
     * Return values:
     *
     *  0:     On successful initialization
     *
     * -ENODEV: If initialization fails. Only close() can be called successfully
     *          by the framework after this.
     */
    int (*initialize)(const struct camera3_device *,
            const camera3_callback_ops_t *callback_ops);
    ...
} camera3_device_ops_t;

可以看出mDevice是camera3_device_t*型別,ops是camera3_device_ops_t*型別,initialize是在哪里賦值的呢?
檔案:"./device/google/marlin/camera/QCamera2/HAL3/QCamera3HWI.cpp"

camera3_device_ops_t QCamera3HardwareInterface::mCameraOps = {
    .initialize                         = QCamera3HardwareInterface::initialize,
    .configure_streams                  = QCamera3HardwareInterface::configure_streams,
    .register_stream_buffers            = NULL,
    .construct_default_request_settings = QCamera3HardwareInterface::construct_default_request_settings,
    .process_capture_request            = QCamera3HardwareInterface::process_capture_request,
    .get_metadata_vendor_tag_ops        = NULL,
    .dump                               = QCamera3HardwareInterface::dump,
    .flush                              = QCamera3HardwareInterface::flush,
    .reserved                           = {0},
};

所以最后呼叫到了QCamera3HardwareInterface::initialize這個靜態函式,
Step7 initialize
檔案:"./device/google/marlin/camera/QCamera2/HAL3/QCamera3HWI.cpp"

/*===========================================================================
* FUNCTION   : initialize
*
* DESCRIPTION: Pass framework callback pointers to HAL
*
* PARAMETERS :
*
*
* RETURN     : Success : 0
*              Failure: -ENODEV
*==========================================================================*/
int QCamera3HardwareInterface::initialize(const struct camera3_device *device,
                                  const camera3_callback_ops_t *callback_ops)
{
    LOGD("E");
    QCamera3HardwareInterface *hw =
        reinterpret_cast<QCamera3HardwareInterface *>(device->priv);
    if (!hw) {
        LOGE("NULL camera device");
        return -ENODEV;
    }
    // 7.1
    int rc = hw->initialize(callback_ops);
    LOGD("X");
    return rc;
}

/*===========================================================================
* FUNCTION   : initialize
*
* DESCRIPTION: Initialize frameworks callback functions
*
* PARAMETERS :
*   @callback_ops : callback function to frameworks
*
* RETURN     :
*
*==========================================================================*/
int QCamera3HardwareInterface::initialize(
        const struct camera3_callback_ops *callback_ops)
{
    ATRACE_CALL();
    int rc;

    LOGI("E :mCameraId = %d mState = %d", mCameraId, mState);
    pthread_mutex_lock(&mMutex);

    // Validate current state
    switch (mState) {
        case OPENED:
            /* valid state */
            break;
        default:
            LOGE("Invalid state %d", mState);
            rc = -ENODEV;
            goto err1;
    }

    rc = initParameters();
    if (rc < 0) {
        LOGE("initParamters failed %d", rc);
        goto err1;
    }
    mCallbackOps = callback_ops;
    // Step8
    mChannelHandle = mCameraHandle->ops->add_channel(
            mCameraHandle->camera_handle, NULL, NULL, this);
    if (mChannelHandle == 0) {
        LOGE("add_channel failed");
        rc = -ENOMEM;
        pthread_mutex_unlock(&mMutex);
        return rc;
    }

    pthread_mutex_unlock(&mMutex);
    mCameraInitialized = true;
    mState = INITIALIZED;
    LOGI("X");
    return 0;

err1:
    pthread_mutex_unlock(&mMutex);
    return rc;
}

進入介面轉化層QCamera3HardwareInterface,注意,callback_ops回呼指標是指CameraDeviceSession實體地址,用mCallbackOps保存回呼指標,通過mCameraHandle->ops->add_channel進入介面層mm_camera_interface,呼叫的方法實際是 mm_camera_interface.c 中的mm_camera_intf_add_channel,
檔案:"hardware/qcom/camera/msm8998/QCamera2/stack/common/mm_camera_interface.h"

/** mm_camera_vtbl_t: virtual table for camera operations
*    @camera_handle : camera handler which uniquely identifies a
*                   camera object
*    @ops : API call table
**/
typedef struct {
    uint32_t camera_handle;
    mm_camera_ops_t *ops;
} mm_camera_vtbl_t;

typedef struct {
    ...
    /** add_channel: fucntion definition for adding a channel
     *    @camera_handle : camer handler
     *    @ch_id : channel handler
     *    @attr : pointer to channel attribute structure
     *    @channel_cb : callbak to handle bundled super buffer
     *    @userdata : user data pointer
     *  Return value: channel id, zero is invalid ch_id
     * Note: attr, channel_cb, and userdata can be NULL if no
     *       superbufCB is needed
     **/
    uint32_t (*add_channel) (uint32_t camera_handle,
                             mm_camera_channel_attr_t *attr,
                             mm_camera_buf_notify_t channel_cb,
                             void *userdata);
    ...
} mm_camera_ops_t;

可以看到mCameraHandle的型別為mm_camera_vtbl_t *,mCameraHandle->ops的型別為mm_camera_ops_t*,
檔案:"hardware/qcom/camera/msm8998/QCamera2/stack/mm-camera-interface/src/mm_camera_interface.c"

/* camera ops v-table */
static mm_camera_ops_t mm_camera_ops = {
    .query_capability = mm_camera_intf_query_capability,
    .register_event_notify = mm_camera_intf_register_event_notify,
    .close_camera = mm_camera_intf_close,
    .set_parms = mm_camera_intf_set_parms,
    .get_parms = mm_camera_intf_get_parms,
    .do_auto_focus = mm_camera_intf_do_auto_focus,
    .cancel_auto_focus = mm_camera_intf_cancel_auto_focus,
    .prepare_snapshot = mm_camera_intf_prepare_snapshot,
    .start_zsl_snapshot = mm_camera_intf_start_zsl_snapshot,
    .stop_zsl_snapshot = mm_camera_intf_stop_zsl_snapshot,
    .map_buf = mm_camera_intf_map_buf,
    .map_bufs = mm_camera_intf_map_bufs,
    .unmap_buf = mm_camera_intf_unmap_buf,
    .add_channel = mm_camera_intf_add_channel,
    ...
}

可以看到,最后呼叫到mm_camera_interface.c檔案的mm_camera_intf_add_channel介面,
Step8 add_channel
檔案:"hardware/qcom/camera/msm8998/QCamera2/stack/mm-camera-interface/src/mm_camera_interface.c"

/*===========================================================================
* FUNCTION   : mm_camera_intf_add_channel
*
* DESCRIPTION: add a channel
*
* PARAMETERS :
*   @camera_handle: camera handle
*   @attr         : bundle attribute of the channel if needed
*   @channel_cb   : callback function for bundle data notify
*   @userdata     : user data ptr
*
* RETURN     : uint32_t type of channel handle
*              0  -- invalid channel handle, meaning the op failed
*              >0 -- successfully added a channel with a valid handle
* NOTE       : if no bundle data notify is needed, meaning each stream in the
*              channel will have its own stream data notify callback, then
*              attr, channel_cb, and userdata can be NULL. In this case,
*              no matching logic will be performed in channel for the bundling.
*==========================================================================*/
static uint32_t mm_camera_intf_add_channel(uint32_t camera_handle,
                                           mm_camera_channel_attr_t *attr,
                                           mm_camera_buf_notify_t channel_cb,
                                           void *userdata)
{
    uint32_t ch_id = 0, aux_ch_id = 0;
    mm_camera_obj_t * my_obj = NULL;
    uint32_t handle = get_main_camera_handle(camera_handle);
    uint32_t aux_handle = get_aux_camera_handle(camera_handle);

    LOGD("E camera_handler = %d", camera_handle);
    if (handle) {
        pthread_mutex_lock(&g_intf_lock);
        my_obj = mm_camera_util_get_camera_by_handler(handle);
        if(my_obj) {
            pthread_mutex_lock(&my_obj->cam_lock);
            pthread_mutex_unlock(&g_intf_lock);
            // Step9
            ch_id = mm_camera_add_channel(my_obj, attr, channel_cb, userdata);
        } else {
            pthread_mutex_unlock(&g_intf_lock);
        }
    }
    ...
    LOGH("camera_handle = %u ch_id = %u X", camera_handle, ch_id);
    return ch_id;
}

通過呼叫實作層的 mm_camera_add_channel 來獲取一個 ch_id,
Step9 mm_camera_add_channel
檔案:"hardware/qcom/camera/msm8998/QCamera2/stack/mm-camera-interface/src/mm_camera.c"

/*===========================================================================
* FUNCTION   : mm_camera_add_channel
*
* DESCRIPTION: add a channel
*
* PARAMETERS :
*   @my_obj       : camera object
*   @attr         : bundle attribute of the channel if needed
*   @channel_cb   : callback function for bundle data notify
*   @userdata     : user data ptr
*
* RETURN     : uint32_t type of channel handle
*              0  -- invalid channel handle, meaning the op failed
*              >0 -- successfully added a channel with a valid handle
* NOTE       : if no bundle data notify is needed, meaning each stream in the
*              channel will have its own stream data notify callback, then
*              attr, channel_cb, and userdata can be NULL. In this case,
*              no matching logic will be performed in channel for the bundling.
*==========================================================================*/
uint32_t mm_camera_add_channel(mm_camera_obj_t *my_obj,
                               mm_camera_channel_attr_t *attr,
                               mm_camera_buf_notify_t channel_cb,
                               void *userdata)
{
    mm_channel_t *ch_obj = NULL;
    uint8_t ch_idx = 0;
    uint32_t ch_hdl = 0;

    for(ch_idx = 0; ch_idx < MM_CAMERA_CHANNEL_MAX; ch_idx++) {
        if (MM_CHANNEL_STATE_NOTUSED == my_obj->ch[ch_idx].state) {
            ch_obj = &my_obj->ch[ch_idx];
            break;
        }
    }

    if (NULL != ch_obj) {
        /* initialize channel obj */
        memset(ch_obj, 0, sizeof(mm_channel_t));
        ch_hdl = mm_camera_util_generate_handler_by_num(my_obj->my_num, ch_idx);
        ch_obj->my_hdl = ch_hdl;
        ch_obj->state = MM_CHANNEL_STATE_STOPPED;
        ch_obj->cam_obj = my_obj;
        pthread_mutex_init(&ch_obj->ch_lock, NULL);
        ch_obj->sessionid = my_obj->sessionid;
        // 9.1
        mm_channel_init(ch_obj, attr, channel_cb, userdata);
    }

    pthread_mutex_unlock(&my_obj->cam_lock);
    return ch_hdl;
}

先找出狀態為NOTUSED的通道channel,再進行通道channel初始化,
檔案:"hardware/qcom/camera/msm8998/QCamera2/stack/mm-camera-interface/src/mm_camera_channel.c"

/*===========================================================================
* FUNCTION   : mm_channel_init
*
* DESCRIPTION: initialize a channel
*
* PARAMETERS :
*   @my_obj       : channel object be to initialized
*   @attr         : bundle attribute of the channel if needed
*   @channel_cb   : callback function for bundle data notify
*   @userdata     : user data ptr
*
* RETURN     : int32_t type of status
*              0  -- success
*              -1 -- failure
* NOTE       : if no bundle data notify is needed, meaning each stream in the
*              channel will have its own stream data notify callback, then
*              attr, channel_cb, and userdata can be NULL. In this case,
*              no matching logic will be performed in channel for the bundling.
*==========================================================================*/
int32_t mm_channel_init(mm_channel_t *my_obj,
                        mm_camera_channel_attr_t *attr,
                        mm_camera_buf_notify_t channel_cb,
                        void *userdata)
{
    int32_t rc = 0;

    my_obj->bundle.super_buf_notify_cb = channel_cb;
    my_obj->bundle.user_data = userdata;
    if (NULL != attr) {
        my_obj->bundle.superbuf_queue.attr = *attr;
    }

    my_obj->num_s_cnt = 0;
    memset(&my_obj->frame_sync, 0, sizeof(my_obj->frame_sync));
    pthread_mutex_init(&my_obj->frame_sync.sync_lock, NULL);
    mm_muxer_frame_sync_queue_init(&my_obj->frame_sync.superbuf_queue);
    my_obj->bundle.is_cb_active = 1;

    LOGD("Launch data poll thread in channel open");
    snprintf(my_obj->poll_thread[0].threadName, THREAD_NAME_SIZE, "CAM_dataPoll");
    mm_camera_poll_thread_launch(&my_obj->poll_thread[0],
                                 MM_CAMERA_POLL_TYPE_DATA);

    /* change state to stopped state */
    my_obj->state = MM_CHANNEL_STATE_STOPPED;
    return rc;
}

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/333835.html

標籤:其他

上一篇:移動開發作業2——RecycleView頁面點擊跳轉設計

下一篇:怎么確定android能否連接某個地址(ping 某個地址)?

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more