android surfaceview 大小_Android 使用Camera2 API采集视频数据
??????? Android 視頻數(shù)據(jù)采集系列的最后一篇出爐了,和前兩篇文章想比,這篇文章從系統(tǒng)API層面進(jìn)行一些探索,涉及到的細(xì)節(jié)更多。初次接觸 Camera2 API 會覺得它的使用有些繁瑣,涉及到的類有些多,不過就像第一次使用Activity, Fragment 的API 一樣,只要多加練習(xí),熟練掌握這些 API 只是時(shí)間問題。
????????Andrid 系統(tǒng)最初提供的操控相機(jī)的 API android.hardware.camera 現(xiàn)已棄用,新的API android.hardware.camera2 在andrid L 上開始使用,這里只討論和學(xué)習(xí) Camera2 的使用。根據(jù)谷歌官方的說法,重新設(shè)計(jì) Camera API 的目的在于大幅提高應(yīng)用對于 Android 設(shè)備上的相機(jī)子系統(tǒng)的控制能力,同時(shí)重新組織 API,提高其效率和可維護(hù)性。借助額外的控制能力,開發(fā)者可以更輕松地在 Android 設(shè)備上構(gòu)建高品質(zhì)的相機(jī)應(yīng)用,這些應(yīng)用可在多種產(chǎn)品上穩(wěn)定運(yùn)行,同時(shí)仍會盡可能使用設(shè)備專用算法來最大限度地提升質(zhì)量和性能。
????????關(guān)于Camera API 和 Camere API2 的對比,官方介紹 Camera2 的視頻里沒有詳細(xì)說明,只是一筆帶過,提到了以下兩點(diǎn):
????1. Camera API 不支持捕捉未被壓縮的畫面,或者不支持在新的硬件上運(yùn)行,預(yù)覽效率被限定在一到三秒每幀;
????2. Camere2 API 拍照的時(shí)間間隔更短,支持在多臺相機(jī)上預(yù)覽,可以直接加特效或者濾膜。
??????? Camera2 API 把攝像頭設(shè)備建模為管道,該管道接收一個(gè)捕獲單個(gè)幀的請求做為輸入,輸出一個(gè)捕獲結(jié)果元數(shù)據(jù)包,以及該請求的一組輸出圖像緩沖區(qū)。這些請求包含有關(guān)幀的捕獲和處理的所有配置信息,其中包括分辨率和像素格式,手動傳感器、鏡頭和閃光燈控件,3A 操作模式,RAW 到 YUV 處理控件等。捕獲單個(gè)幀的請求按順序處理,并且多個(gè)請求可以同時(shí)進(jìn)行,攝像頭設(shè)備處理數(shù)據(jù)需要經(jīng)過多道工序的加工處理,在大多數(shù)Android 設(shè)備上需要有多個(gè)正在運(yùn)行的請求才能保持完整的幀率。
????????完整的相機(jī)模型可以用下面一張圖表示:
????????如果想全面了解相機(jī)模型的細(xì)節(jié),這張圖比較合適,可是這么多大大小小的框框和沒見過的類對于我這樣的小白不太友好,我們來看一張精簡的圖片:
????????第二張圖是相機(jī)核心操作的模型圖,其中出現(xiàn)的一些類 CameraDevice, CameraRequest等看起來有些陌生,不用著急待會兒給他們做一一介紹。
????????為了方便理解,我們可以把攝像頭設(shè)備看作一個(gè)工廠車間,捕獲一張圖片的過程看作車間里一道完整的流水線,把 Camera2 API 相關(guān)的類看作車間里的師傅們,這些師傅各司其職,協(xié)同工作完成捕獲圖像的任務(wù)。下面我們來看完成這個(gè)任務(wù)需要的師傅們。
??? ??? CameraManager,類似于LocationManager、ConnectivityManager ,是一個(gè)系統(tǒng)級別的服務(wù)管理器,負(fù)責(zé)枚舉、查詢和打開可用的相機(jī)設(shè)備。
??? ??? CameraDevice,是連接到 Android 設(shè)備的單個(gè)相機(jī)的表示形式,可以對以高幀速率捕獲圖像和后期處理進(jìn)行細(xì)粒度控制。CameraDevice 描述了硬件設(shè)備以及該設(shè)備的可用設(shè)置和輸出參數(shù)。這些信息通過 CameraCharacteristics 對象提供。
????????CameraCharacteristics,用來描述 CameraDevice 的屬性,比如支持的 JPEG 縮略圖大小等, 這些屬性對于給定的 CameraDevice 是固定的,可以通過CameraManager.getCameraCharacteristics 查詢。
??? ??? CameraCaptureSession,是 CameraDevice 捕獲圖像的會話,用于捕獲來自攝像機(jī)的圖像或重新處理先前在同一會話中捕獲的圖像。創(chuàng)建 CameraCaptureSession 需要配置攝像頭設(shè)備的內(nèi)部管道并分配用于將圖像發(fā)送到所需目標(biāo)的內(nèi)存緩沖區(qū),是一項(xiàng)耗費(fèi)資源的異步操作,可能需要幾百毫秒。
??? ??? CaptureRequest,是從攝像機(jī)設(shè)備捕獲單個(gè)圖像所需的一組不變的設(shè)置,包含捕獲硬件(傳感器,鏡頭,閃光燈),處理管線,控制算法和輸出緩沖區(qū)的配置。還包含捕獲后的圖像數(shù)據(jù)發(fā)送的目標(biāo) Surface 列表。
??? ??? CaptureResult,是 CameraDevice 處理 CaptureRequest之后產(chǎn)生的,從圖像傳感器捕獲的單個(gè)圖像結(jié)果的子集。包含捕獲硬件(傳感器,鏡頭,閃光燈),處理管線,控制算法和輸出緩沖區(qū)的最終配置的子集。
??? ??? Image,是與媒體源(例如MediaCodec或CameraDevice)一起使用的單個(gè)完整圖像緩沖區(qū)。Image 允許通過一個(gè)或多個(gè) ByteBuffer 高效的直接訪問 Image 中的像素?cái)?shù)據(jù)。每個(gè)緩沖區(qū)數(shù)據(jù)封裝在一個(gè)描述像素?cái)?shù)據(jù)的 Plane中,由于這種直接訪問的方式,Image不能直接用作UI資源。
??????? Image 通常是由硬件組件直接生成或使用的,是整個(gè)系統(tǒng)共享的有限資源,應(yīng)在不再需要時(shí)及時(shí)關(guān)閉。例如,當(dāng)使用 ImageReader 類從各種媒體源中讀取圖像時(shí),一旦達(dá)到 ImageReader.getMaxImages 的數(shù)量限制,不關(guān)閉舊的 Image 對象將阻止新圖像的可用性。
??? ??? ImageReader 人如其名,用來讀取 Image 數(shù)據(jù),也可以做為 Image 的存儲緩沖區(qū),允許應(yīng)用程序直接訪問渲染到 Surface 中的圖像數(shù)據(jù)。CameraDevice 捕獲的圖像數(shù)據(jù)被封裝在 Image 對象中,Surface 使用 ImageReader讀取這些數(shù)據(jù)。使用 ImageReader 可以同時(shí)訪問多個(gè)Imagge對象,發(fā)送到 ImageReader 的圖像將排隊(duì)等待,ImageReader 的工作方式類似于生產(chǎn)者消費(fèi)者模式,直到之前的圖像被訪問取走,新的圖像才能被存到隊(duì)列里。由于內(nèi)存限制,如果 ImageReader 未能以等于生產(chǎn)速率的速率獲取和釋放圖像,則圖像源為了嘗試把圖像渲染到 Surface上,最終將停止發(fā)送或者丟掉一些圖像。
??? ??? SurfaceView,老熟人了,音視頻開發(fā)中出鏡率最高的 View 之一, 用于渲染 Camera 設(shè)備的預(yù)覽畫面和捕獲的圖像。
??? ??? DngCreator,用于將原始像素?cái)?shù)據(jù)寫入 DNG 文件的類。通常與CameraDevice 可用的 ImageFormat.RAW_SENSOR 緩沖區(qū)一起使用,或與應(yīng)用程序生成的 Bayer-type 原始像素?cái)?shù)據(jù)一起使用。
??? ??? DNG(Digital Negative)文件格式是 Adobe 公司發(fā)表的一種跨平臺文件格式,旨在統(tǒng)一數(shù)碼相機(jī)廣泛使用的圖像文件格式“RAW”。DNG 文件允許在用戶定義的顏色空間中定義像素?cái)?shù)據(jù)和關(guān)聯(lián)的元數(shù)據(jù),該元數(shù)據(jù)允許在后期處理期間將該像素?cái)?shù)據(jù)轉(zhuǎn)換為標(biāo)準(zhǔn)CIE XYZ顏色空間。
????????以上就是車間里參與捕獲圖像的主要的師傅們,等師傅們準(zhǔn)備就緒后,就可以開始動工了。從準(zhǔn)備到捕獲一張圖像一共需要經(jīng)過五個(gè)流程:
準(zhǔn)備渲染圖像的 SurfaceView
????????整個(gè)捕獲圖像的過程中,SurfaceView 負(fù)責(zé)相機(jī)畫面的渲染工作,它內(nèi)部使用 Surface 來展示這些圖像。Surface 的創(chuàng)建是一個(gè)異步操作,等 Surface 創(chuàng)建完畢后就可以進(jìn)行下一步操作。
打開攝像頭設(shè)備,初始化 CameraDevice
????????這一步需要參與的類有 CameraManager、CameraId, 以及 CameraHandler。打開攝像頭是一個(gè)耗時(shí)操作,為了不阻塞主線程,需要在新的線程里執(zhí)行。告訴 CameraManager 要打開的攝像頭ID(前置或者后置攝像頭)以及執(zhí)行該操作的handler, CameraManager 會通過接口回調(diào)通知你操作的結(jié)果,打開攝像頭成功,或者錯(cuò)誤,或者攝像頭不可用。整個(gè)異步操作使用 kotlin 協(xié)程完成:
/**?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,開啟圖像預(yù)覽
????????攝像頭成功打開以后,就可以調(diào)用設(shè)備的硬件和軟件資源,創(chuàng)建圖像預(yù)覽了,這一步需要 CameraDevice、CameraSession、ImageReader 協(xié)作完成。ImageReader 被創(chuàng)建作為捕獲靜態(tài)圖像的緩存,使用 CameraDevice 創(chuàng)建 CameraSession時(shí),把用于預(yù)覽圖像的 SurfaceView 中的 Surface 和 ImageReader 中的 Surface 作為圖像幀數(shù)據(jù)的接收者。調(diào)用 CameraSession.setRepeatingRequest()啟動攝像頭連續(xù)畫面預(yù)覽。
//?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)????????到這里,捕獲圖像前的準(zhǔn)備工作已經(jīng)完成,下面的操作就是等待用戶按下捕獲圖像的按鈕,拍攝圖像和保存圖像結(jié)果。
4. 捕獲圖像
????????捕獲圖像的操作由 CameraCaptureSession 完成,它使用保存的 CameraDevice 創(chuàng)建一個(gè) CaptureRequest.Builder 用來設(shè)置捕獲圖像的參數(shù)以及展示圖像的 Surface, 把 CaptureRequest 和捕獲圖像后的回調(diào)函數(shù) CameraCaptureSession.CaptureCallback 交給 CameraCaptureSession 后,它會通過 CaptureCallback 及時(shí)通知外界捕獲圖像的進(jìn)度。這里使用 ImageReader 作為捕獲圖像的緩沖區(qū),捕獲完成后,CameraCaptureSession 返回捕獲結(jié)果 TotalCaptureResult。捕獲圖像屬于IO 密集型操作,同樣需要異步實(shí)現(xiàn):
/*** 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,直接保存圖像的字節(jié)流,如果是原始格式 RAW_SENSOR,需要使用 DngCreator 把圖像數(shù)據(jù)保存為跨平臺的 DNG 格式,方便以后使用,保存成功以后返回一個(gè) 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 捕獲一張圖像的任務(wù),使用 Camera2 API 可以拿到圖像的原始數(shù)據(jù)用作后期各種處理,并且對捕獲圖像的過程進(jìn)行更細(xì)粒度的控制,獲取完成的示例代碼請移步 https://github.com/android/camera-sample 或者 https://github.com/Hiwensen/StreamingTour
??????? Android 設(shè)備采集視頻數(shù)據(jù)系列也到此結(jié)束,三種方法:使用系統(tǒng)已安裝的相機(jī)應(yīng)用,使用 Jetpack ?CameraX 庫或者使用 Camera2 API,各有優(yōu)缺點(diǎn)和不同的適用場景,總有一種能滿足你的需求。文中只介紹了基本的捕獲圖像的用法,至于錄制視頻和更多功能,后期有更多時(shí)間了繼續(xù)探索。
總結(jié)
以上是生活随笔為你收集整理的android surfaceview 大小_Android 使用Camera2 API采集视频数据的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: “差池玉绳高”下一句是什么
- 下一篇: 安康看不孕不育比较好的医院推荐