android surfaceview 大小_Android 使用Camera2 API采集視頻數據

??????? Android 視頻數據采集系列的最后一篇出爐了,和前兩篇文章想比,這篇文章從系統API層面進行一些探索,涉及到的細節更多。初次接觸 Camera2 API 會覺得它的使用有些繁瑣,涉及到的類有些多,不過就像第一次使用Activity, Fragment 的API 一樣,只要多加練習,熟練掌握這些 API 只是時間問題。

????????Andrid 系統最初提供的操控相機的 API android.hardware.camera 現已棄用,新的API android.hardware.camera2 在andrid L 上開始使用,這里只討論和學習 Camera2 的使用。根據谷歌官方的說法,重新設計 Camera API 的目的在于大幅提高應用對于 Android 設備上的相機子系統的控制能力,同時重新組織 API,提高其效率和可維護性。借助額外的控制能力,開發者可以更輕松地在 Android 設備上構建高品質的相機應用,這些應用可在多種產品上穩定運行,同時仍會盡可能使用設備專用算法來最大限度地提升質量和性能。

????????關于Camera API 和 Camere API2 的對比,官方介紹 Camera2 的視頻里沒有詳細說明,只是一筆帶過,提到了以下兩點:

????1. Camera API 不支持捕捉未被壓縮的畫面,或者不支持在新的硬件上運行,預覽效率被限定在一到三秒每幀;

????2. Camere2 API 拍照的時間間隔更短,支持在多臺相機上預覽,可以直接加特效或者濾膜。

??????? Camera2 API 把攝像頭設備建模為管道,該管道接收一個捕獲單個幀的請求做為輸入,輸出一個捕獲結果元數據包,以及該請求的一組輸出圖像緩沖區。這些請求包含有關幀的捕獲和處理的所有配置信息,其中包括分辨率和像素格式,手動傳感器、鏡頭和閃光燈控件,3A 操作模式,RAW 到 YUV 處理控件等。捕獲單個幀的請求按順序處理,并且多個請求可以同時進行,攝像頭設備處理數據需要經過多道工序的加工處理,在大多數Android 設備上需要有多個正在運行的請求才能保持完整的幀率。

????????完整的相機模型可以用下面一張圖表示:

665c9c3de57b2c20a0076cf062df454e.png

????????如果想全面了解相機模型的細節,這張圖比較合適,可是這么多大大小小的框框和沒見過的類對于我這樣的小白不太友好,我們來看一張精簡的圖片:

4b1a17cabdb323e5fc090fae03fbc0ab.png

????????第二張圖是相機核心操作的模型圖,其中出現的一些類 CameraDevice, CameraRequest等看起來有些陌生,不用著急待會兒給他們做一一介紹。

????????為了方便理解,我們可以把攝像頭設備看作一個工廠車間,捕獲一張圖片的過程看作車間里一道完整的流水線,把 Camera2 API 相關的類看作車間里的師傅們,這些師傅各司其職,協同工作完成捕獲圖像的任務。下面我們來看完成這個任務需要的師傅們。

??? ??? CameraManager,類似于LocationManager、ConnectivityManager ,是一個系統級別的服務管理器,負責枚舉、查詢和打開可用的相機設備。

??? ??? CameraDevice,是連接到 Android 設備的單個相機的表示形式,可以對以高幀速率捕獲圖像和后期處理進行細粒度控制。CameraDevice 描述了硬件設備以及該設備的可用設置和輸出參數。這些信息通過 CameraCharacteristics 對象提供。

????????CameraCharacteristics,用來描述 CameraDevice 的屬性,比如支持的 JPEG 縮略圖大小等, 這些屬性對于給定的 CameraDevice 是固定的,可以通過CameraManager.getCameraCharacteristics 查詢。

??? ??? CameraCaptureSession,是 CameraDevice 捕獲圖像的會話,用于捕獲來自攝像機的圖像或重新處理先前在同一會話中捕獲的圖像。創建 CameraCaptureSession 需要配置攝像頭設備的內部管道并分配用于將圖像發送到所需目標的內存緩沖區,是一項耗費資源的異步操作,可能需要幾百毫秒。

??? ??? CaptureRequest,是從攝像機設備捕獲單個圖像所需的一組不變的設置,包含捕獲硬件(傳感器,鏡頭,閃光燈),處理管線,控制算法和輸出緩沖區的配置。還包含捕獲后的圖像數據發送的目標 Surface 列表。

??? ??? CaptureResult,是 CameraDevice 處理 CaptureRequest之后產生的,從圖像傳感器捕獲的單個圖像結果的子集。包含捕獲硬件(傳感器,鏡頭,閃光燈),處理管線,控制算法和輸出緩沖區的最終配置的子集。

??? ??? Image,是與媒體源(例如MediaCodec或CameraDevice)一起使用的單個完整圖像緩沖區。Image 允許通過一個或多個 ByteBuffer 高效的直接訪問 Image 中的像素數據。每個緩沖區數據封裝在一個描述像素數據的 Plane中,由于這種直接訪問的方式,Image不能直接用作UI資源。

??????? Image 通常是由硬件組件直接生成或使用的,是整個系統共享的有限資源,應在不再需要時及時關閉。例如,當使用 ImageReader 類從各種媒體源中讀取圖像時,一旦達到 ImageReader.getMaxImages 的數量限制,不關閉舊的 Image 對象將阻止新圖像的可用性。

??? ??? ImageReader 人如其名,用來讀取 Image 數據,也可以做為 Image 的存儲緩沖區,允許應用程序直接訪問渲染到 Surface 中的圖像數據。CameraDevice 捕獲的圖像數據被封裝在 Image 對象中,Surface 使用 ImageReader讀取這些數據。使用 ImageReader 可以同時訪問多個Imagge對象,發送到 ImageReader 的圖像將排隊等待,ImageReader 的工作方式類似于生產者消費者模式,直到之前的圖像被訪問取走,新的圖像才能被存到隊列里。由于內存限制,如果 ImageReader 未能以等于生產速率的速率獲取和釋放圖像,則圖像源為了嘗試把圖像渲染到 Surface上,最終將停止發送或者丟掉一些圖像。

??? ??? SurfaceView,老熟人了,音視頻開發中出鏡率最高的 View 之一, 用于渲染 Camera 設備的預覽畫面和捕獲的圖像。

??? ??? DngCreator,用于將原始像素數據寫入 DNG 文件的類。通常與CameraDevice 可用的 ImageFormat.RAW_SENSOR 緩沖區一起使用,或與應用程序生成的 Bayer-type 原始像素數據一起使用。

??? ??? DNG(Digital Negative)文件格式是 Adobe 公司發表的一種跨平臺文件格式,旨在統一數碼相機廣泛使用的圖像文件格式“RAW”。DNG 文件允許在用戶定義的顏色空間中定義像素數據和關聯的元數據,該元數據允許在后期處理期間將該像素數據轉換為標準CIE XYZ顏色空間。

????????以上就是車間里參與捕獲圖像的主要的師傅們,等師傅們準備就緒后,就可以開始動工了。從準備到捕獲一張圖像一共需要經過五個流程:

89dd216758ae8b45839d9a37d757863d.png

  1. 準備渲染圖像的 SurfaceView

????????整個捕獲圖像的過程中,SurfaceView 負責相機畫面的渲染工作,它內部使用 Surface 來展示這些圖像。Surface 的創建是一個異步操作,等 Surface 創建完畢后就可以進行下一步操作。

  1. 打開攝像頭設備,初始化 CameraDevice

????????這一步需要參與的類有 CameraManager、CameraId, 以及 CameraHandler。打開攝像頭是一個耗時操作,為了不阻塞主線程,需要在新的線程里執行。告訴 CameraManager 要打開的攝像頭ID(前置或者后置攝像頭)以及執行該操作的handler, CameraManager 會通過接口回調通知你操作的結果,打開攝像頭成功,或者錯誤,或者攝像頭不可用。整個異步操作使用 kotlin 協程完成:

/**?Opens?the?camera?and?returns?the?opened?device?(as?the?result?of?the?suspend?coroutine)?*/@ExperimentalCoroutinesApi@SuppressLint("MissingPermission")private suspend fun openCamera(    cameraManager: CameraManager,    cameraId: String,    cameraHandler: Handler): CameraDevice =   suspendCancellableCoroutine { cont ->???? cameraManager.openCamera(cameraId,?object?:?CameraDevice.StateCallback()?{ ??????????override?fun?onOpened(camera:?CameraDevice)?=?cont.resume(camera)??????????override?fun?onDisconnected(camera:?CameraDevice)?{              Log.w(TAG, "Camera $cameraId has been disconnected")              requireActivity().finish()??????????}          override fun onError(camera: CameraDevice, error: Int) {               val msg = when (error) {                   ERROR_CAMERA_DEVICE -> "Fatal device"                   ERROR_CAMERA_DISABLED -> "Device policy"                   ERROR_CAMERA_IN_USE -> "Camera in use"                   ERROR_CAMERA_SERVICE -> "Fatal service"???????????????????ERROR_MAX_CAMERAS_IN_USE?->?"Maximum?cameras?in?use"???????????????????else?->?"Unknown"??????????????? }               val exception = RuntimeException("Camera $cameraId error:($error) $msg")???????????????Log.e(TAG,?exception.message,?exception)???????????????if?(cont.isActive)?cont.resumeWithException(exception)??????????}??????},?cameraHandler)?}

?3. 配置 CameraCaptureSession,開啟圖像預覽

????????攝像頭成功打開以后,就可以調用設備的硬件和軟件資源,創建圖像預覽了,這一步需要 CameraDevice、CameraSession、ImageReader 協作完成。ImageReader 被創建作為捕獲靜態圖像的緩存,使用 CameraDevice 創建 CameraSession時,把用于預覽圖像的 SurfaceView 中的 Surface 和 ImageReader 中的 Surface 作為圖像幀數據的接收者。調用 CameraSession.setRepeatingRequest()啟動攝像頭連續畫面預覽。

//?Initialize?an?image?reader?which?will?be?used?to?capture?still?photosval size = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)!!            .getOutputSizes(args.pixelFormat).maxBy { it.height * it.width }!!imageReader = ImageReader.newInstance(size.width, size.height, args.pixelFormat, IMAGE_BUFFER_SIZE)// Creates list of surfaces where the camera will output framesval targets = listOf(viewFinder.holder.surface, imageReader.surface)// Start a capture session using our open camera and list of surfaces where frames will gosession = createCaptureSession(camera, targets, cameraHandler)val captureRequest = camera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW).apply {            addTarget(viewFinder.holder.surface)}// Keep sending the capture request as frequently as possible until the//?session?is?torn?down?or?session.stopRepeating()?is?calledsession.setRepeatingRequest(captureRequest.build(), null, cameraHandler)

????????到這里,捕獲圖像前的準備工作已經完成,下面的操作就是等待用戶按下捕獲圖像的按鈕,拍攝圖像和保存圖像結果。

4. 捕獲圖像

????????捕獲圖像的操作由 CameraCaptureSession 完成,它使用保存的 CameraDevice 創建一個 CaptureRequest.Builder 用來設置捕獲圖像的參數以及展示圖像的 Surface, 把 CaptureRequest 和捕獲圖像后的回調函數 CameraCaptureSession.CaptureCallback 交給 CameraCaptureSession 后,它會通過 CaptureCallback 及時通知外界捕獲圖像的進度。這里使用 ImageReader 作為捕獲圖像的緩沖區,捕獲完成后,CameraCaptureSession 返回捕獲結果 TotalCaptureResult。捕獲圖像屬于IO 密集型操作,同樣需要異步實現:

/*** Helper function used to capture a still image using the [CameraDevice.TEMPLATE_STILL_CAPTURE]template.* It performs synchronization between the [CaptureResult] and the [Image] resulting* from the single capture, and outputs a [CombinedCaptureResult] object.*/private suspend fun takePhoto(): CombinedCaptureResult = suspendCoroutine { cont ->// Fulsh any images left in the image reader@Suppress("ControlFlowWithEmptyBody") while (imageReader.acquireNextImage() != null) {} // Start a new image queueval imageQueue = ArrayBlockingQueue(IMAGE_BUFFER_SIZE)imageReader.setOnImageAvailableListener({ reader ->??????val?image?=?reader.acquireNextImage()??????Log.d(TAG,?"Image?available?in?queue:${image.timestamp}")??????imageQueue.add(image)}, imageReaderHandler)val captureResult = session.device.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE)    .apply { addTarget(imageReader.surface) }session.capture(captureResult.build(), object : CameraCaptureSession.CaptureCallback() {     override fun onCaptureStarted(         session: CameraCaptureSession,         request: CaptureRequest,         timestamp: Long,         frameNumber: Long?????)?{?????????super.onCaptureStarted(session,?request,?timestamp,?frameNumber)          // Start the animation to inform the user that capture begin          viewFinder.post(animationTask)        }???????override?fun?onCaptureCompleted(           session: CameraCaptureSession,           request: CaptureRequest,???????????result:?TotalCaptureResult       ) {       // Save capture result and other operations...        }, cameraHandler)}

5. 保存圖像

????????拿到捕獲的圖像后,到了最后一道工序,保存捕獲的圖像。如果圖像的格式是 JPEG 或者 DEPTH_JPEG,直接保存圖像的字節流,如果是原始格式 RAW_SENSOR,需要使用 DngCreator 把圖像數據保存為跨平臺的 DNG 格式,方便以后使用,保存成功以后返回一個 File 文件:

/** Helper function used to save a [CombinedCaptureResult] into a [File] */private suspend fun saveResult(result: CombinedCaptureResult): File = suspendCoroutine { cont ->   when (result.format) {       // When the format is JPEG or DEPTH JPEG we can simply save the bytes as-is       ImageFormat.JPEG, ImageFormat.DEPTH_JPEG -> {         val buffer = result.image.planes[0].buffer?????????val?bytes?=?ByteArray(buffer.remaining()).apply?{?buffer.get(this)?}?????????try?{              val output = createFile(requireContext(), "jpg")              FileOutputStream(output).use { it.write(bytes) }              cont.resume(output)??????????????}?catch?(exc:?IOException)?{                Log.e(TAG, "Unable to write JPEG image to file", exc)                cont.resumeWithException(exc)                }????????}      // When the format is RAW we use the DngCreator utility library      ImageFormat.RAW_SENSOR -> {???????????val?dngCreator?=?DngCreator(characteristics,?result.metadata)           try {                val output = createFile(requireContext(), "dng")                FileOutputStream(output).use { dngCreator.writeImage(it, result.image) }                cont.resume(output)                } catch (exc: IOException) {                    Log.e(TAG, "Unable to write DNG image to file", exc)                    cont.resumeWithException(exc)                }            }       // No other formats are supported by this sample       else -> {???????????val?exc?=?RuntimeException("Unknown?image?format:?${result.image.format}")           Log.e(TAG, exc.message, exc)           cont.resumeWithException(exc)???????}??? }?}

????????到這里就完成了使用 Camera2 API 捕獲一張圖像的任務,使用 Camera2 API 可以拿到圖像的原始數據用作后期各種處理,并且對捕獲圖像的過程進行更細粒度的控制,獲取完成的示例代碼請移步 https://github.com/android/camera-sample 或者 https://github.com/Hiwensen/StreamingTour

??????? Android 設備采集視頻數據系列也到此結束,三種方法:使用系統已安裝的相機應用,使用 Jetpack ?CameraX 庫或者使用 Camera2 API,各有優缺點和不同的適用場景,總有一種能滿足你的需求。文中只介紹了基本的捕獲圖像的用法,至于錄制視頻和更多功能,后期有更多時間了繼續探索。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/374186.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/374186.shtml
英文地址,請注明出處:http://en.pswp.cn/news/374186.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

java生成隨機字符串

學習java comparable特性時候,定義如下Student類,需要需要隨機添加學生姓名以及學號和成績,這是java如何隨機生成名字,根據我的查詢,我找到目前java庫支持兩種方法。 1. org.apache.commons.lang3.RandomStringUtils類…

使用SharedPreferenes存取數據

//使用SharedPreference存儲數據 public void on(View view){     //獲取用戶名和密碼     String nameeditText1.getText().toString();     String numbereditText2.getText().toString();     //判斷checkBox是否為勾選      CheckBox box(CheckBox…

使用Java VisualVM分析您的應用程序

當您需要發現應用程序的哪個部分消耗更多的CPU或內存時,必須使用探查器執行此操作。 默認情況下,Sun JDK中附帶的一個探查器是Java VisualVM。 這個事件探查器非常簡單易用,功能強大。 在這篇文章中,我們將看到如何安裝它并使用它…

ArcSDE for SQL Server安裝及在ArcMap中創建ArcSDE連接

ArcSDE for SQL Server安裝及在ArcMap中創建ArcSDE連接 原文:ArcSDE for SQL Server安裝及在ArcMap中創建ArcSDE連接安裝ArcSDE for SQL Server,最后一步成功后的界面如下:在ArcMap中創建ArcSDE連接,截圖如下:posted on 2016-08-0…

java反射main方法參數注意

public class ReflectMethodMain {public static void main(String[] args) throws Exception {Method methodMainTestArgument.class.getMethod("main", String[].class);/*jdk 1.4之前的版本不支持可變參數,對組類的參數會自動拆包然后用來匹配數據參數…

python調用c函數傳字符串參數_Python使用ctypes模塊調用DLL函數之傳遞數值、指針與字符串參數...

在Python語言中,可以使用ctypes模塊調用其它如C語言編寫的動態鏈接庫DLL文件中的函數,在提高軟件運行效率的同時,也可以充分利用目前市面上各種第三方的DLL庫函數,以擴充Python軟件的功能及應用領域,減少重復編寫代碼、…

沁恒CH554 KEIL環境搭建

首先下載WCHISPTool_Setup.exe http://www.wch.cn/products/CH554.html 123這三個可下載的都下吧,后面開發都要用的 安裝好后運行,菜單欄上,功能->添加WCH MCU到KEIL器件庫 這時候在KEIL安裝目錄里面的UV4文件夾下可以看到wch.cdb的文件…

【CV論文閱讀】Rank Pooling for Action Recognition

這是期刊論文的版本,不是會議論文的版本。看了論文之后,只能說,太TM聰明了。膜拜~~ 視頻的表示方法有很多,一般是把它看作幀的序列。論文提出一種新的方法去表示視頻,用ranking function的參數編碼視頻的幀序列。它使用…

java不要在常量和變量中出現易混淆的字母

public class proposal {public static void main(String[] args) {long i1l; System.out.println("i的兩倍是:"(ii));System.out.println("i的兩倍是:"ii); //注意此處和上面的有很大的區別} }輸出:211/** 注意:* …

VS2019 WPF制作OTA上位機(一)新建工程

首先創建新項目,文件 -> 新建 -> 項目 下拉菜單選擇C#和Window,選擇WPF應用程序,下一步 輸入項目名,下一步 這里選擇.NET 5.0,也可以選擇其他的,個人習慣.NET,點擊創建 這時候出現初始…

戶籍恢復需要體檢嗎_腦梗死后腳麻能恢復嗎?需要多久能恢復呢?

腦梗死之后腳部麻木,這個有一部分是能夠恢復的,但是相對而言,恢復的時間比較長,在臨床當中出現腳麻主要是因為梗死破壞了患者的感覺神經中樞從而造成。腳部感覺麻木,瘙癢或者是有螞蟻在上面爬的感覺。而且有的更加嚴重…

Alpha版本測試報告

一、測試計劃 Alpha版本即將發布,我們組織隊員進行這一版本的測試。 測試主要針對兩方面:瀏覽器兼容性和功能完善性。 測試分兼容性測試與功能完善性兩部分,兼容性測試分Windows操作系統、Linux系操作系統、Mac OS X操作系統以及手機端Androi…

在J2SE應用程序中模擬CDI的會話和請求范圍

我們目前正在考慮將Naked Objects框架重構為使用JSR-330(依賴注入)和面向EE的老大哥JSR-299(CDI)。 使用香草JSR-330是不費吹灰之力的,但是我們想利用JSR-299中的一些不錯的功能(例如事件和裝飾器&#xff…

VS2019 WPF制作OTA上位機(二)獲取bin文件路徑

OTA升級是通過無線通信遠程把bin文件內容傳輸到單片機,完成升級。 因此上位機需要獲取bin文件的路徑,讀取bin文件內容,將內容分割依次發送(因為單片機的接收緩存不會開得和bin文件一樣大(十幾K甚至幾十K)&a…

java 線程“生產/消費”模型1

/*資源類*/ public class ShareValue {private int total;public ShareValue(int total){this.totaltotal;}//生產void putValue(int value){totalvalue;}//消費資源int getValue(int value){if(total-value>0){total-value;}else{valuetotal;total0;System.out.println(&qu…

PHP - 代碼分離

總代碼&#xff1a; <?php/** Version&#xff1a;1.0* CreateTime&#xff1a;2015年11月11日* Author&#xff1a;HF_Ultrastrong*///引入公共文件,在公共文件中創建&#xff0c;相對于項目的絕對路徑require dirname(__FILE__)./includes/common.inc.php; ?&…

原生js封裝table表格操作,獲取任意行列td,任意單行單列方法

V1.001更新增加findTable-min.js 本次更新&#xff0c;優化了代碼性能方面&#xff0c;增加了部分新功能&#xff0c;可以獲取多個table表格批量操作。 考慮到本人后面的項目中可能涉及到大量的表格操作&#xff0c;提前先封了 一個簡單的操作方法&#xff0c;日后再加完善&…

Spring陷阱:事務測試被認為是有害的

Spring殺手級功能之一是容器內集成測試 。 盡管EJB多年來一直缺乏此功能&#xff08;Java EE 6終于解決了這個問題&#xff0c;但是我還沒測試過&#xff09;&#xff0c;但是Spring從一開始就允許您從Web層開始&#xff0c;通過所有服務來測試整個堆棧。到數據庫的方式。 數據…

python xlwt寫入已有表_Python中,添加寫入數據到已經存在的Excel文件

1.安裝xlrd、xlwt、xlutilshttps://pypi.org/project/xlutils/pip安裝&#xff1a;cmd下輸入&#xff1a;pip install xlrd #讀取exclepip install xlwt #寫入exclepip install xlutils #操作 Excel 文件的實用工具&#xff0c;如復制、分割、篩選等2.代碼主要部分實現import x…

java線程“生產/消費”模型2

/* 資源類 */ class ShareValue {private int total;//判斷對象是否為空private boolean isEmptytrue;//判斷對象是否已滿private boolean isFulltrue;public ShareValue(int total) {this.total total;if(total>0) isEmptyfalse;if(total<1000) isFullfalse;}/** sync…