)
1. Android13 Camera2 多流輸出適配OutputConfiguration 與 Stream usecase 到底解決了什么問題如果你在做 Android Camera2 開發(fā)大概率遇到過這種場景預覽要一路流、拍照要一路流、錄像還要一路流三路流同時開的時候要么幀率掉得厲害要么某一路直接創(chuàng)建失敗。Android 13 之前我們能控制的只有 Surface 的尺寸和格式至于「這路流到底是給預覽用的還是給錄像用的」系統(tǒng)并不知道只能靠 HAL 自己猜。猜錯了功耗和延遲就上去了。Android 13 在 Camera2 里補上了這塊拼圖核心是兩個東西OutputConfiguration和Stream usecase。OutputConfiguration 是 Android 10 就引入的但 Android 13 給它加了 Mirror、Timestamp base、Dynamic range profile 這些新能力Stream usecase 則是 Android 13 真正落地的一個「語義標簽」機制讓你告訴底層「這路流是 PREVIEW、STILL_CAPTURE 還是 VIDEO_RECORD」。打個比方以前的 Surface 就像寄快遞只寫地址不寫物品類型快遞員只能按默認方式處理現在你可以標注「易碎」「冷藏」物流系統(tǒng)就能提前分配對應的資源。Stream usecase 就是這個「物品類型標簽」它直接影響 ISP、Scaler 的資源分配策略。這篇面向的是已經在用 Camera2、準備在 Android 13 設備上適配多流輸出的開發(fā)者。我會給出可直接復制的 OutputConfiguration 配置骨架、Stream usecase 的設置方式以及在真機上驗證流組合是否生效的具體步驟。涉及的關鍵檢索詞包括 Android13 Camera2 OutputConfiguration 配置、Stream usecase 設置、SCALER_MANDATORY_USE_CASE_STREAM_COMBINATIONS 查詢等都會在代碼里體現。需要先明確一點Stream usecase 不是所有設備都支持。你得先查REQUEST_AVAILABLE_CAPABILITIES里有沒有REQUEST_AVAILABLE_CAPABILITIES_STREAM_USE_CASE沒有的話設了也白設系統(tǒng)會忽略。這個判斷邏輯我會在第三節(jié)的代碼里寫清楚。另外多流組合不是隨便配的。Android 13 提供了SCALER_MANDATORY_USE_CASE_STREAM_COMBINATIONS這個靜態(tài)屬性它告訴你「哪些 usecase 組合是設備一定支持的」。你按它給的組合去配成功率最高自己亂配可能創(chuàng)建 Session 時直接拋異常。這是本篇要重點講的部分。2. TaoToken 前置準備用模型對話快速核對 Camera2 API 簽名與常量Camera2 的 API 簽名和常量值經常記混尤其是 Android 13 新增的這一批。我自己的做法是在寫代碼前先用模型對話把關鍵 API 的簽名和常量對照一遍避免編譯期才發(fā)現參數類型不對。TaoToken 的模型對話入口在這里https://taotoken.net/api?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite 。你可以直接問它「Android 13 OutputConfiguration setStreamUseCase 的參數類型是什么」「SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_RECORD 的常量值是多少」這類問題它會給出對應的 API 說明。為什么要在寫 Camera2 代碼前做這一步因為 Android 13 的 Camera2 新增 API 有幾個坑第一setStreamUseCase(long)的參數是 long不是 int。很多人習慣性寫 int編譯不過。這個 long 值來自SCALER_AVAILABLE_STREAM_USE_CASES_*常量。第二setDynamicRangeProfile(long)也是 long而且它和 output format 強綁定——只有ImageFormat.YCBCR_P010或ImageFormat.PRIVATE才能設 10bit HDR profile。你如果拿一個 YUV_420_888 的 Surface 去設 HLG10運行時會報錯。第三setMirrorMode(int)只影響 Buffer 的 Transform matrix不會真的去翻轉像素數據。這個語義如果理解錯了后面顯示方向對不上會排查很久。用模型對話把這些簽名和約束先過一遍比直接翻 AOSP 源碼快得多。我試過把一段報錯的堆棧貼進去問它能定位到是哪個 setter 的參數類型或取值不對。拿到確認后的 API 信息再回到 Android Studio 里寫代碼編譯一次過的概率會高很多。這一步不涉及任何環(huán)境配置就是純查證幾分鐘的事。如果你后面要做的是長期編碼或者 Agent 類的自動化任務可以考慮 Coding Planhttps://taotoken.net/api?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite 。但就本篇這個場景模型對話足夠用了。3. 可復制配置OutputConfiguration 與 Stream usecase 代碼骨架這一節(jié)給出完整的配置代碼。核心思路是先查設備支持哪些 usecase再按 mandatory 組合去配 OutputConfiguration最后創(chuàng)建 Session。先看能力查詢部分。這段代碼判斷設備是否支持 Stream usecase并拿到支持的 usecase 列表// 查詢設備是否支持 Stream usecase CameraCharacteristics characteristics cameraManager.getCameraCharacteristics(cameraId); int[] capabilities characteristics.get( CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES); boolean supportStreamUseCase false; if (capabilities ! null) { for (int cap : capabilities) { if (cap CameraCharacteristics .REQUEST_AVAILABLE_CAPABILITIES_STREAM_USE_CASE) { supportStreamUseCase true; break; } } } // 拿到當前 Camera 支持的 stream usecase 列表 long[] availableUseCases null; if (supportStreamUseCase) { availableUseCases characteristics.get( CameraCharacteristics.SCALER_AVAILABLE_STREAM_USE_CASES); }接下來是查詢 mandatory 組合。這個屬性返回的是一個 long 數組每兩個一組表示一個組合或者按文檔定義的編碼方式解析。實際使用時最穩(wěn)妥的做法是遍歷它找到包含你需要的 usecase 的組合// 查詢設備一定支持的 usecase 組合 long[] mandatoryCombinations characteristics.get( CameraCharacteristics.SCALER_MANDATORY_USE_CASE_STREAM_COMBINATIONS);然后是核心的 OutputConfiguration 配置。假設我們要配三路流預覽、拍照、錄像。每路流創(chuàng)建一個 OutputConfiguration設置對應的 usecase// 預覽流尺寸 1920x1080PRIVATE 格式 SurfaceTexture previewTexture new SurfaceTexture(0); previewTexture.setDefaultBufferSize(1920, 1080); Surface previewSurface new Surface(previewTexture); OutputConfiguration previewConfig new OutputConfiguration(previewSurface); if (supportStreamUseCase) { previewConfig.setStreamUseCase( CameraCharacteristics.SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW); } // 拍照流尺寸 4032x3024JPEG 格式 ImageReader stillReader ImageReader.newInstance( 4032, 3024, ImageFormat.JPEG, 2); OutputConfiguration stillConfig new OutputConfiguration( stillReader.getSurface()); if (supportStreamUseCase) { stillConfig.setStreamUseCase( CameraCharacteristics.SCALER_AVAILABLE_STREAM_USE_CASES_STILL_CAPTURE); } // 錄像流尺寸 1920x1080PRIVATE 格式 MediaRecorder recorder new MediaRecorder(); // ... recorder 配置省略 ... OutputConfiguration recordConfig new OutputConfiguration( recorder.getSurface()); if (supportStreamUseCase) { recordConfig.setStreamUseCase( CameraCharacteristics.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_RECORD); }如果你要做 10bit HDR 輸出需要額外設置 dynamic range profile并且 format 必須是 YCBCR_P010 或 PRIVATE// 10bit HDR 輸出流 ImageReader hdrReader ImageReader.newInstance( 1920, 1080, ImageFormat.YCBCR_P010, 2); OutputConfiguration hdrConfig new OutputConfiguration( hdrReader.getSurface()); if (supportStreamUseCase) { hdrConfig.setStreamUseCase( CameraCharacteristics.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_RECORD); } // 設置 HDR profile需先確認設備支持 DynamicRangeProfiles profiles characteristics.get( CameraCharacteristics.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES); if (profiles ! null profiles.getSupportedProfiles() .contains(DynamicRangeProfiles.HLG10)) { hdrConfig.setDynamicRangeProfile(DynamicRangeProfiles.HLG10); }最后創(chuàng)建 Session。注意這里用的是SessionConfiguration它接受 OutputConfiguration 列表ListOutputConfiguration outputConfigs new ArrayList(); outputConfigs.add(previewConfig); outputConfigs.add(stillConfig); outputConfigs.add(recordConfig); SessionConfiguration sessionConfig new SessionConfiguration( SessionConfiguration.SESSION_REGULAR, outputConfigs, new HandlerExecutor(backgroundHandler), new CameraCaptureSession.StateCallback() { Override public void onConfigured(CameraCaptureSession session) { // Session 創(chuàng)建成功可以下發(fā)請求了 } Override public void onConfigureFailed(CameraCaptureSession session) { // 配置失敗檢查流組合是否被支持 } }); cameraDevice.createCaptureSession(sessionConfig);這段代碼里setStreamUseCase和setDynamicRangeProfile都做了能力判斷不支持就跳過不會因為設了不支持的 usecase 而崩潰。這是適配多機型的關鍵。4. 真機驗證確認流組合與輸出配置是否生效代碼寫完只是第一步真正要確認的是「設備到底認不認你配的 usecase」。這一節(jié)給出真機驗證的具體步驟。第一步打印設備支持的 usecase 列表。在onOpened回調里加日志long[] useCases characteristics.get( CameraCharacteristics.SCALER_AVAILABLE_STREAM_USE_CASES); if (useCases ! null) { for (long uc : useCases) { Log.d(TAG, supported usecase: Long.toHexString(uc)); } }對照日志里的值確認你用的PREVIEW、STILL_CAPTURE、VIDEO_RECORD是否在列表里。如果某個不在說明這臺設備不支持該 usecase你設了也會被忽略。第二步驗證 Session 是否創(chuàng)建成功。如果onConfigureFailed被調用大概率是流組合不被支持。這時候去查SCALER_MANDATORY_USE_CASE_STREAM_COMBINATIONS看你的組合是否在 mandatory 列表里。不在的話換一個 mandatory 支持的組合再試。第三步驗證 usecase 是否真的生效。最直接的方法是抓CaptureResult看CaptureResult里有沒有對應的 usecase 回傳。不過更實用的方法是看功耗和幀率設置VIDEO_RECORDusecase 后錄像流的幀率應該更穩(wěn)定掉幀更少設置PREVIEWusecase 后預覽延遲應該更低。第四步驗證 Mirror 和 Timestamp base。Mirror 只影響 Transform matrix你可以通過OutputConfiguration.getMirrorMode()確認設置是否被接受。Timestamp base 則可以通過對比不同流的 timestamp 來驗證// 在 onCaptureCompleted 里打印 timestamp Log.d(TAG, stream timestamp: result.get(CaptureResult.SENSOR_TIMESTAMP));如果設置了TIMESTAMP_BASE_SENSOR那 timestamp 應該和 sensor 的時間基準一致設置TIMESTAMP_BASE_REALTIME則和系統(tǒng)實時時鐘對齊。第五步驗證 10bit HDR。設置DynamicRangeProfile.HLG10后檢查輸出 buffer 的 format 是否為YCBCR_P010。如果是說明 HDR 流配置生效了。同時可以對比 HDR 和 SDR 流的畫面亮度范圍HDR 流的高光細節(jié)應該更豐富。實測下來最容易出問題的是流組合。很多設備雖然支持單個 usecase但不支持你想要的組合。所以第三步的 mandatory 組合查詢一定要做別跳過。5. 本篇常見報錯排查401、local proxy failed、reading choices、OAuth這一節(jié)整理幾個在配置過程中可能遇到的報錯以及對應的排查方向。報錯一IllegalArgumentException: stream use case not supported這個報錯通常出現在setStreamUseCase時傳了一個設備不支持的 usecase。排查方法先打印SCALER_AVAILABLE_STREAM_USE_CASES確認你用的常量在列表里。如果不在就不要設或者換一個支持的。報錯二onConfigureFailed被調用但沒有明確異常信息這是流組合不被支持。排查方法查SCALER_MANDATORY_USE_CASE_STREAM_COMBINATIONS把你的組合和 mandatory 列表對比。如果組合不在列表里嘗試減少流數量或者換用 mandatory 支持的組合。報錯三local proxy failed或網絡請求相關錯誤如果你在查 API 文檔或調用模型對話時遇到local proxy failed先檢查網絡配置。TaoToken 的 API 入口是 https://taotoken.net/api?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite 確認請求地址沒有拼錯。如果是 401說明 API Key 無效或過期去 API Keys 頁面重新生成https://taotoken.net/api?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite 。報錯四reading choices相關錯誤這個通常出現在解析模型返回結果時。如果你用模型對話查 API 簽名返回的 JSON 里choices字段解析失敗檢查一下請求的 model 參數是否正確以及返回內容是否被截斷。報錯五OAuth 相關錯誤如果你用的是需要 OAuth 的接入方式檢查 token 是否過期。OAuth 流程的配置可以參考接入文檔https://taotoken.net/api?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite 。報錯六setDynamicRangeProfile拋異常這個報錯的原因是 output format 不是YCBCR_P010或PRIVATE。排查方法檢查創(chuàng)建 ImageReader 時用的 format必須是這兩個之一才能設 HDR profile。報錯七Mirror 設置后畫面方向不對Mirror 只影響 Transform matrix不會翻轉像素。如果你在顯示端沒有正確處理 Transform matrix畫面方向就會不對。排查方法檢查顯示端的 matrix 應用邏輯確保它讀取了 OutputConfiguration 的 mirror mode。6. 語義一致 CTA繼續(xù)深入 Camera2 與 Android13 適配Camera2 的適配工作很多時候卡在「設備支持什么」和「我配了什么」之間的信息差上。Android 13 的 OutputConfiguration 和 Stream usecase 把一部分控制權交回給了開發(fā)者但也要求開發(fā)者更清楚設備的能力邊界。如果你在配置過程中需要反復核對 API 簽名、常量值、報錯含義用模型對話會省很多時間https://taotoken.net/api?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite 。把報錯堆棧貼進去它能幫你定位到具體的 setter 或參數。需要生成 API Key 的話入口在這里https://taotoken.net/api?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite 。接入文檔里有完整的請求示例和參數說明https://taotoken.net/api?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite 。最后給一個實用建議在真機驗證時先把SCALER_MANDATORY_USE_CASE_STREAM_COMBINATIONS打印出來按它給的組合去配成功率最高。自己組合的流即使單個 usecase 都支持也可能因為資源沖突而創(chuàng)建失敗。這個屬性是 Android 13 給開發(fā)者的「安全組合清單」別浪費它。