拆解:從請(qǐng)求-響應(yīng)模型到Pipeline實(shí)現(xiàn))
我做了這么多年Camera HAL硬件抽象層開發(fā)帶過好幾個(gè)項(xiàng)目的相機(jī)系統(tǒng)落地最常被問到的一個(gè)問題就是HAL3到底比HAL1強(qiáng)在哪為什么Google費(fèi)那么大勁非要推一套新的架構(gòu)如果你只把HAL3當(dāng)成“多給了幾個(gè)回調(diào)接口”的升級(jí)版那后面寫代碼、調(diào)性能、適配廠商硬件的時(shí)候一定會(huì)被它的復(fù)雜度搞得焦頭爛額。這篇就把HAL3的架構(gòu)設(shè)計(jì)拆開揉碎講清楚適合剛接觸相機(jī)底層的BSP工程師、上層框架開發(fā)以及想搞明白“手機(jī)拍照那一瞬間系統(tǒng)里發(fā)生了什么”的應(yīng)用層同學(xué)。我先給個(gè)結(jié)論HAL3最大的變化不是接口變多了而是把“拍照的控制權(quán)”從底層硬件邏輯手里完整交還給了上層框架底層只負(fù)責(zé)執(zhí)行請(qǐng)求并返回結(jié)果。這個(gè)模型看似簡(jiǎn)單真落地的時(shí)候全是細(xì)節(jié)。1. 設(shè)計(jì)動(dòng)機(jī)從單一大流程到請(qǐng)求-響應(yīng)管道1.1 HAL1時(shí)代解決不了的兩個(gè)核心問題先聊幾句HAL1。在Android 4.4之前的相機(jī)架構(gòu)里HAL1暴露的是一個(gè)大而全的靜態(tài)能力接口預(yù)覽、錄制、拍照、對(duì)焦、曝光各自對(duì)應(yīng)獨(dú)立的函數(shù)入口底層維護(hù)著一整套狀態(tài)機(jī)。上層調(diào)用startPreview()之后預(yù)覽數(shù)據(jù)持續(xù)不斷地從底層頂上來上層想插一腳改個(gè)參數(shù)都得看底層臉色。這個(gè)模型對(duì)早期功能機(jī)時(shí)代的單攝方案夠用但到了雙攝、三攝、多路RAW、計(jì)算攝影流行之后暴露了根子上的問題控制粒度太粗??蚣軟]法拿到“上一幀是用什么曝光參數(shù)拍的”這種精細(xì)信息更別說讓每一幀走不同的感知管線。數(shù)據(jù)流路徑僵化。預(yù)覽流、拍照流、視頻流在HAL1里是寫死的切換分辨率或者增加一路處理流底層往往需要重新初始化整個(gè)相機(jī)設(shè)備中間會(huì)有肉眼可見的黑屏和卡頓。我當(dāng)年在某個(gè)項(xiàng)目上調(diào)HAL1的多路預(yù)覽為了在輔助預(yù)覽流和主預(yù)覽流之間切換不得不寫各種條件判斷去猜上層意圖體驗(yàn)就是兩個(gè)字別扭。架構(gòu)層面沒有“請(qǐng)求”這個(gè)概念所有操作都是“命令”命令只能走固定的路徑一旦路徑不通就歇菜。1.2 HAL3的核心設(shè)計(jì)思想把控制權(quán)上移HAL3在Android 4.4以后逐步成為主流到Android 8.0開始強(qiáng)制所有新設(shè)備必須支持HAL3。它的核心思想可以拆成三條請(qǐng)求-響應(yīng)模型。框架側(cè)每次下發(fā)一個(gè)CaptureRequest采集請(qǐng)求里面指明需要哪些輸出流Stream、每路流的格式和分辨率、3A自動(dòng)曝光/自動(dòng)對(duì)焦/自動(dòng)白平衡的模式和固定參數(shù)、所需的幀號(hào)等等。底層拿到這個(gè)請(qǐng)求按需執(zhí)行一幀采集異步返回CaptureResult采集結(jié)果。Pipeline管道化。底層不再暴露“預(yù)覽”“拍照”“錄制”等業(yè)務(wù)概念而是暴露一組抽象的Stream輸出流??蚣芟襁B接水管一樣把Stream接到不同的消費(fèi)端預(yù)覽Surface、照片回調(diào)、視頻編碼器誰能吃什么數(shù)據(jù)、按什么節(jié)奏吃由上層統(tǒng)一編排。逐幀Per-frame控制。請(qǐng)求是按幀走的這意味著同一時(shí)刻可以有多個(gè)請(qǐng)求在流水線上排隊(duì)而每一個(gè)請(qǐng)求的曝光、增益、對(duì)焦位置、色彩空間都可以不一樣。這一點(diǎn)是計(jì)算攝影和HDR算法的基石——多幀合成模式采用的正是這個(gè)能力。一句話總結(jié)HAL1是“底層告訴我能做什么我挑著用”HAL3是“我告訴底層這一幀該怎么做底層只負(fù)責(zé)執(zhí)行”。2. 架構(gòu)分層CameraService、CameraProvider 與 HAL3 的邊界2.1 一整套鏈路里的每個(gè)角色畫一條從App到硬件的完整鏈路大家就明白HAL3的位置了App調(diào)用openCamera()后請(qǐng)求經(jīng)由CameraManager傳給CameraService框架核心服務(wù)。CameraService通過AIDL接口與CameraProvider進(jìn)程通信。CameraProvider由設(shè)備廠商實(shí)現(xiàn)在Android 8.0之后被單獨(dú)放進(jìn)一個(gè)進(jìn)程HIDL/直接AIDL服務(wù)主要目的就是隔離——相機(jī)廠商代碼的崩潰不能拖垮整個(gè)系統(tǒng)。CameraProvider內(nèi)部創(chuàng)建具體的CameraDevice這個(gè)設(shè)備再往底層走就是HAL3接口定義的camera3_device_t。HAL3層本身不是一個(gè)服務(wù)它只是一組C接口。真正的實(shí)現(xiàn)在廠商的android.hardware.camera.provider服務(wù)里。但架構(gòu)上所有上層流程都是圍繞這組接口展開的。2.2 CameraDeviceSession 與 Pipeline 的職責(zé)劃分在HAL3的接口里最重要的兩個(gè)對(duì)象是camera3_device_ops設(shè)備級(jí)的操作包括初始化、設(shè)置請(qǐng)求流、下發(fā)請(qǐng)求、以及獲取幀元數(shù)據(jù)。這是上層和硬件對(duì)話的主要入口。camera3_callback_ops底層往上報(bào)結(jié)果和事件的結(jié)構(gòu)體包括采集結(jié)果process_capture_result和異步錯(cuò)誤通知notify。對(duì)照到廠商實(shí)現(xiàn)里通常有一個(gè)CameraDeviceSession類來承載上述邏輯。Session內(nèi)部維護(hù)自己的Pipeline管線管線由一個(gè)個(gè)Node節(jié)點(diǎn)組成——比如ISP節(jié)點(diǎn)、降噪節(jié)點(diǎn)、HDR節(jié)點(diǎn)、格式轉(zhuǎn)換節(jié)點(diǎn)。請(qǐng)求從進(jìn)入Session到最終出幀會(huì)流經(jīng)整條Pipeline每個(gè)節(jié)點(diǎn)可以對(duì)該幀做修改或者產(chǎn)生自己的元數(shù)據(jù)。我在實(shí)際項(xiàng)目里見過一個(gè)坑有些新人會(huì)把Pipeline設(shè)計(jì)成每個(gè)Stream一條獨(dú)立管線結(jié)果一個(gè)請(qǐng)求要同時(shí)出預(yù)覽和拍照兩路幀底層得分別處理兩遍同樣的算力開銷。更合理的設(shè)計(jì)是共享大部分前級(jí)節(jié)點(diǎn)比如RAW處理、3A統(tǒng)計(jì)只在最末端分化出不同的格式轉(zhuǎn)換和輸出節(jié)點(diǎn)。HAL3接口沒有規(guī)定Pipeline內(nèi)部怎么搭這給了廠商很大的重構(gòu)空間但架構(gòu)設(shè)計(jì)必須要意識(shí)到“多流共享”才是性能關(guān)鍵。2.3 三個(gè)核心數(shù)據(jù)結(jié)構(gòu)的真實(shí)含義這里我挑最重要的三個(gè)數(shù)據(jù)結(jié)構(gòu)展開它們分別是camera3_stream_t輸出流camera3_capture_request_t采集請(qǐng)求camera3_capture_result_t采集結(jié)果camera3_stream_t并不只是一個(gè)格式分辨率的結(jié)構(gòu)體它還帶有usage使用場(chǎng)景標(biāo)志和max_buffers等字段。比如預(yù)覽流通常要求GRALLOC_USAGE_HW_TEXTURE而視頻流會(huì)加上GRALLOC_USAGE_HW_VIDEO_ENCODER底層要根據(jù)usage來決定Buffer緩沖區(qū)應(yīng)該分配在哪個(gè)內(nèi)存區(qū)域、是否需要走專用硬件通路。這部分如果配置錯(cuò)了最典型的現(xiàn)象是預(yù)覽正常但錄像花屏或者錄像正常但拍照掉幀。camera3_capture_request_t攜帶幀號(hào)frame_number、輸入Buffer、輸出Buffer數(shù)組、以及一個(gè)androidMetadata指針。幀號(hào)的作用貫穿整個(gè)往返流程底層返回結(jié)果時(shí)也要攜帶同樣的幀號(hào)這樣才能一一對(duì)應(yīng)。由于請(qǐng)求是按幀提交的底層必須能夠支持多幀并發(fā)在管線里這也是HAL3接口比較硬性的性能要求。camera3_capture_result_t則是一整套完整的“回執(zhí)”包含本幀的實(shí)際曝光參數(shù)、3A狀態(tài)、輸出Buffer最終被填到了哪個(gè)槽位、以及已經(jīng)完成的物理相機(jī)元數(shù)據(jù)。注意一個(gè)請(qǐng)求可以對(duì)應(yīng)多個(gè)Buffer底層是可以分批返回結(jié)果的——典型場(chǎng)景是讓預(yù)覽數(shù)據(jù)先到場(chǎng)照片Buffer晚一點(diǎn)再到。3. 核心實(shí)現(xiàn)并發(fā)請(qǐng)求、Buffer管理與元數(shù)據(jù)驗(yàn)證3.1 請(qǐng)求是怎么在管道里跑起來的標(biāo)準(zhǔn)流程不需要從一個(gè)線程發(fā)起但非常依賴異步回調(diào)的配合。大致按這個(gè)順序來App調(diào)用框架API框架匯總出當(dāng)前需要的一組輸出Stream調(diào)用configure_streams交給HAL。HAL根據(jù)Stream列表配置內(nèi)部Pipeline分配Buffer池并返回其支持的組合是否全部可用。之后每一幀框架構(gòu)造camera3_capture_request_t調(diào)用process_capture_request下發(fā)。HAL收到請(qǐng)求后不能阻塞等待相機(jī)出幀而是立即返回OK在內(nèi)部編排Pipeline開始工作。等到某一幀完成HAL通過回調(diào)線程主動(dòng)調(diào)用process_capture_result把結(jié)果推回給框架。這個(gè)模型意味著HAL層必須維護(hù)一個(gè)請(qǐng)求隊(duì)列和一個(gè)結(jié)果隊(duì)列而且必須正確處理多請(qǐng)求并發(fā)。我踩過一個(gè)典型的并發(fā)問題HAL在處理第三幀時(shí)第一幀的Buffer還在Camera ISP圖像信號(hào)處理器里沒寫完成結(jié)果就把Buffer提前還給了框架導(dǎo)致畫面出現(xiàn)撕裂。排查下來是因?yàn)榈讓覲ipeline里的Buffer狀態(tài)追蹤管理得太粗糙沒有區(qū)分“硬件正在寫入”和“已可安全讀取”兩個(gè)狀態(tài)。這個(gè)問題的通用解法是在Pipeline的每個(gè)輸出節(jié)點(diǎn)都做雙緩沖或者環(huán)形緩沖配合柵欄Fence機(jī)制。Android的相機(jī)Buffer在framework側(cè)有對(duì)應(yīng)Fence同步柵欄但HAL內(nèi)部如果自己管理Buffer輪轉(zhuǎn)也得自己維護(hù)一輪狀態(tài)機(jī)。核心原則就是Buffer在自己的生命周期內(nèi)必須明確處于可寫、鎖定、可讀三種狀態(tài)之一永遠(yuǎn)不要直接跳狀態(tài)。3.2 Buffer 管理與柵欄機(jī)制Buffer管理是整個(gè)HAL3穩(wěn)定性的命門。在典型場(chǎng)景里每個(gè)輸出Stream對(duì)應(yīng)一個(gè)由Gralloc分配的Buffer隊(duì)列??蚣軓腂ufferQueue里dequeue取出一塊Buffer填進(jìn)請(qǐng)求交給HALHAL完成采集后通過柵欄告知框架“這塊Buffer可以消費(fèi)了”。這里最關(guān)鍵的兩個(gè)環(huán)節(jié)上游Buffer的格式和硬件對(duì)齊。不同ISP圖像信號(hào)處理器對(duì)Buffer的寬高有對(duì)齊要求常見的是16對(duì)齊、32對(duì)齊或者64對(duì)齊如果框架給的預(yù)覽分辨率沒有對(duì)齊HAL必須內(nèi)部拷貝或者補(bǔ)邊padding否則硬件會(huì)直接打爆內(nèi)存。實(shí)際開發(fā)中底層必須對(duì)每個(gè)Stream做嚴(yán)格的格式合法性校驗(yàn)不能只靠上層自覺。**多個(gè)輸出流共享同一幀數(shù)據(jù) 。HAL3的一個(gè)請(qǐng)求經(jīng)常同時(shí)掛了兩到三個(gè)輸出Buffer比如預(yù)覽YUV流、拍照J(rèn)PEG流和一個(gè)視頻流。最省算力的方案是同一份YUV數(shù)據(jù)通過硬件縮放/編碼節(jié)點(diǎn)復(fù)制后填充到不同Buffer里。但這個(gè)方案要求底層好好設(shè)計(jì)Pipeline各節(jié)點(diǎn)的依賴關(guān)系而不能簡(jiǎn)單地在每個(gè)Buffer上重新跑一遍ISP。我當(dāng)時(shí)調(diào)試一個(gè)MIPI攝像頭串行接口傳感器輸出的場(chǎng)景時(shí)就吃過緩沖尺寸的虧。傳感器輸出RAW10格式一行數(shù)據(jù)算出來的字節(jié)數(shù)和實(shí)際硬件對(duì)齊后差了幾個(gè)像素導(dǎo)致橫向跑位非常難查。后來干脆在底層把“傳感器payload長(zhǎng)度”和“Gralloc Buffer的stride每行字節(jié)對(duì)齊規(guī)則”全部打印出來比對(duì)才發(fā)現(xiàn)是ISP配置里的裁剪區(qū)域沒算對(duì)。在HAL3里任何Buffer布局不一致都會(huì)直接導(dǎo)致畫面錯(cuò)位調(diào)試手段之一就是逐行核對(duì)stride。3.3 Metadata 的雙向流轉(zhuǎn)與 3A 狀態(tài)的維護(hù)除了圖像數(shù)據(jù)HAL3里還跑著一套元數(shù)據(jù)Metadata系統(tǒng)。Android相機(jī)框架會(huì)下發(fā)一串控制元數(shù)據(jù)比如曝光時(shí)間、ISO、對(duì)焦位置、色調(diào)映射曲線、3A模式等。HAL在返回CaptureResult時(shí)需要回填對(duì)應(yīng)的實(shí)際值比如實(shí)際使用的曝光時(shí)間和增益以及當(dāng)前3A狀態(tài)是否收斂、是否鎖定。這塊在架構(gòu)上最容易被忽略的是框架下發(fā)的Metadata是全局的但HAL內(nèi)部可能同時(shí)處理多路物理相機(jī)比如雙攝。Android 9之后加入了物理相機(jī)PhysicalCamera的支持允許一個(gè)邏輯相機(jī)包含兩個(gè)物理子相機(jī)而每個(gè)物理子相機(jī)都有自己的結(jié)果元數(shù)據(jù)。這時(shí)候的結(jié)果回調(diào)代碼里metadata的組織方式就不是單層的了需要套一個(gè)映射邏輯相機(jī)ID - 物理相機(jī)ID數(shù)組。對(duì)于純做單攝的項(xiàng)目來說Metadata的關(guān)鍵還是拍完的幀和元數(shù)據(jù)必須時(shí)刻同步。如果你用幀號(hào)來對(duì)應(yīng)請(qǐng)求那么一個(gè)Metadata結(jié)果也必須帶上對(duì)應(yīng)的幀號(hào)這是回調(diào)里必須嚴(yán)格遵守的基本協(xié)議。我在框架側(cè)見過一個(gè)很隱蔽的bugHAL返回結(jié)果的幀號(hào)跟Buffer實(shí)際對(duì)應(yīng)關(guān)系錯(cuò)了一位導(dǎo)致前幾幀的曝光值永遠(yuǎn)滯后偏色只在某些特定亮度下偶現(xiàn)非常難查。提示HAL3 沒有“當(dāng)前參數(shù)查詢”這種接口。所有參數(shù)都是隨請(qǐng)求下發(fā)、隨結(jié)果返回因此任何狀態(tài)查詢?cè)诩軜?gòu)上都必須轉(zhuǎn)為請(qǐng)求-響應(yīng)的模式。如果你想調(diào)試“攝像頭此刻的曝光時(shí)間是多少”沒有捷徑只能給一幀請(qǐng)求看它對(duì)應(yīng)的Result值。4. 性能優(yōu)化與并發(fā)設(shè)計(jì)幀率怎么穩(wěn)住4.1 請(qǐng)求排隊(duì)深度與調(diào)度影響幀率在HAL3接口下請(qǐng)求會(huì)一直下發(fā)底層需要維持一定深度的請(qǐng)求隊(duì)列才能讓ISP流水線保持滿負(fù)荷。但隊(duì)列太深會(huì)增大時(shí)延太淺則容易斷流掉幀。這一塊沒有一個(gè)固定最優(yōu)解要求實(shí)際上層預(yù)覽/錄像的幀率需求。比如30fps下一幀時(shí)間約33ms考慮到ISP處理、3A統(tǒng)計(jì)更新、格式轉(zhuǎn)換Pipeline內(nèi)部至少要有23幀的緩沖才能平滑銜接。60fps則要求每幀16ms內(nèi)完成全部處理緩沖深度基本只能做到2幀否則實(shí)時(shí)性跟不上。我做過一個(gè)對(duì)比實(shí)驗(yàn)把請(qǐng)求隊(duì)列深度從3降到1預(yù)覽畫面的端到端時(shí)延能從200ms左右降到110ms左右但掉幀率開始上升。所以如果是做視頻通話這類對(duì)時(shí)延極敏感的場(chǎng)景側(cè)重點(diǎn)就要放在減少排隊(duì)深度和縮短流水線節(jié)點(diǎn)路徑上如果做的是抓拍連續(xù)多幀反而需要深一點(diǎn)的隊(duì)列來保證連續(xù)幀的穩(wěn)定供給。4.2 多Stream場(chǎng)景的性能沖突與共享策略項(xiàng)目里最常見的性能沖突是“預(yù)覽拍照”同時(shí)進(jìn)行。很多方案把拍照做成單獨(dú)的JPEG編碼Stream同時(shí)保留一路YUV預(yù)覽Stream。問題在于兩路流如果各自走獨(dú)立的格式轉(zhuǎn)換和編碼節(jié)點(diǎn)GPU和DSP的負(fù)載立刻翻倍發(fā)熱和掉幀隨之而來。更合理的架構(gòu)是在Pipeline里做一個(gè)共享的Raw/YUV中間節(jié)點(diǎn)讓預(yù)覽和拍照共用這個(gè)節(jié)點(diǎn)的輸出再用兩個(gè)末端節(jié)點(diǎn)把數(shù)據(jù)分別轉(zhuǎn)成YUV預(yù)覽格式和JPEG編碼輸入。這樣拍照瞬間不需要重新跑一遍主ISP鏈路系統(tǒng)只多了JPEG編碼成本。這個(gè)設(shè)計(jì)思想在HAL3架構(gòu)里很常見節(jié)點(diǎn)之間通過Buffer池連接多個(gè)末端可以消費(fèi)同一個(gè)中間節(jié)點(diǎn)的輸出。但要注意共享節(jié)點(diǎn)輸出的Buffer生命周期管理不能出岔子——如果JPEG編碼還在讀這塊共享Buffer預(yù)覽節(jié)點(diǎn)卻在下一幀把它寫壞了就會(huì)出現(xiàn)“拍照后幾幀預(yù)覽模糊/花屏”的典型故障。4.3 同步與異步回調(diào)線程的設(shè)計(jì)原則HAL3接口要求process_capture_request不能阻塞所有耗時(shí)操作都必須異步完成。這意味著在廠商實(shí)現(xiàn)里必須有明確的線程劃分請(qǐng)求接收線程、Pipeline執(zhí)行線程池、結(jié)果回調(diào)線程。三個(gè)線程之間的數(shù)據(jù)用隊(duì)列解耦。處理不好線程最常見的就是死鎖。某項(xiàng)目中CameraService下發(fā)flush請(qǐng)求時(shí)會(huì)等待HAL把所有在途請(qǐng)求處理完再回調(diào)確認(rèn)。如果HAL的回調(diào)線程恰好也在等待框架側(cè)的數(shù)據(jù)到達(dá)比如等GPU釋放Buffer這就構(gòu)成了循環(huán)等待直接卡死整個(gè)相機(jī)進(jìn)程。調(diào)試這類問題方法很直接但很有效給每個(gè)HAL的關(guān)鍵函數(shù)入口加上排隊(duì)超時(shí)統(tǒng)計(jì)之后把線程棧dump下來排查。我在實(shí)戰(zhàn)中發(fā)現(xiàn)80%的“相機(jī)黑屏卡死”都和這種交叉等待有關(guān)而不是ISP硬件本身壞了。5. 常見問題與調(diào)試技巧實(shí)錄5.1 疑難雜癥Top 5癥狀可能原因排查方法預(yù)覽畫面偏色/發(fā)灰元數(shù)據(jù)沒回填色彩校正矩陣ColorCorrection對(duì)比HAL返回的android.colorCorrection.*與預(yù)期值錄像音畫不同步視頻流的Buffer時(shí)間戳timestamp不準(zhǔn)或幀號(hào)對(duì)應(yīng)錯(cuò)位檢查Result里的時(shí)間戳是否來自同一硬件時(shí)鐘源連續(xù)拍攝時(shí)掉速Pipeline末端節(jié)點(diǎn)成了瓶頸用systrace或Perfetto抓取各個(gè)節(jié)點(diǎn)的耗時(shí)分布切換前后攝偶爾黑屏Stream配置變化后BufferPool沒有完全重建檢查configure_streams后是否殘留舊Buffer引用超級(jí)夜景多幀合成撕裂底層請(qǐng)求深度不夠?qū)е聨蛄杏锌斩丛O(shè)置足夠深度并在Result里保持幀號(hào)連續(xù)5.2 調(diào)試三板斧打印、Perfetto、攝像頭數(shù)據(jù)流追蹤搞HAL3調(diào)試以下三件事缺一不可把元數(shù)據(jù)打開。開發(fā)初期直接在代碼里加一段解析邏輯把每次process_capture_request的幀號(hào)和關(guān)鍵參數(shù)以及每次process_capture_result的幀號(hào)和3A狀態(tài)全部打印出來。只要幀號(hào)對(duì)不上立刻能抓住數(shù)據(jù)流錯(cuò)位的線索。用Perfetto/systrace抓取全局行為。在AOSP環(huán)境下相機(jī)的關(guān)鍵節(jié)點(diǎn)都加了trace點(diǎn)直接在systrace里過濾Camera就能看到請(qǐng)求下發(fā)、結(jié)果回調(diào)的時(shí)間線。HAL內(nèi)部如果自己沒加trace標(biāo)記一定要自己加上否則性能問題排查相當(dāng)于盲人摸象。AOSP Camera CTS 測(cè)試會(huì)給你精準(zhǔn)的報(bào)錯(cuò)定位。Google在測(cè)試?yán)飳?duì)HAL3的回調(diào)行為有嚴(yán)格預(yù)期跑一遍android.hardware.cts.CameraTest能過濾掉絕大多數(shù)協(xié)議層面的小bug。5.3 給廠商接口守護(hù)的3個(gè)建議最后幾個(gè)經(jīng)驗(yàn)心得都是踩過坑之后沉淀下來的configure_streams階段就把不支持的組合直接拒掉不要等到第一幀數(shù)據(jù)上來了才發(fā)現(xiàn)放不下。早期拒絕的日志是最容易定位的。result回調(diào)的順序可以亂但幀號(hào)必須對(duì)應(yīng)。Google是允許亂序返回結(jié)果以提高實(shí)時(shí)性的但如果幀號(hào)對(duì)不上上層狀態(tài)機(jī)就會(huì)亂很容易造成UI卡頓。Buffer Fence永遠(yuǎn)不要隨便忽略。有些HAL為了省事在返回Buffer時(shí)直接跳過Fence等待結(jié)果導(dǎo)致上層去讀還沒寫完的數(shù)據(jù)。該等的時(shí)候必須等寧慢勿錯(cuò)。我個(gè)人在實(shí)際操作中的體會(huì)是HAL3這套架構(gòu)最大的門檻不是接口數(shù)量而是思維方式的轉(zhuǎn)變。你在實(shí)現(xiàn)每個(gè)接口時(shí)都必須在腦子里過一遍“這個(gè)請(qǐng)求對(duì)應(yīng)哪一幀、數(shù)據(jù)最后被誰消費(fèi)、Buffer狀態(tài)是否安全”想清楚這三件事HAL3項(xiàng)目就成功了一半。另外建議剛接手HAL3的工程師第一件事就是去把camera3.h頭文件逐行讀兩遍比上任何框架課都管用。動(dòng)手跑一個(gè)CamX或Open Source的相機(jī)Pipeline Demo跑通了再改自己的硬件抽象層推進(jìn)速度會(huì)快很多。