• 一、简介
  • 二、源码分析
    • 2.1 build.gradle
    • 2.2 代码结构
    • 2.3 变量
      • 2.3.1 lensFacing
      • 2.3.2 preview
      • 2.3.3 Image capture
      • 2.3.4 Image Analyzer
      • 2.3.5 camera
      • 2.3.6 cameraProvider
    • 2.4 初始化流程
    • 2.5 拍照
    • 2.6 Release
  • 三、相关练习
  • 附录 CameraFragment源码:

一、简介

本片文章主要针对官方提供的CameraX实现进行简要的解析,从而学会如何编写一个简单的CameraX实例的相机Demo。本篇文章会分析如下几大块:

  1. CameraX实例实现的关键变量
  2. 官方是如何对CameraX实例进行初始化的
  3. 官方是如何使用CameraX实例进行拍照的
  4. 释放实例等其他模块

相关文章和资源推荐:

  1. Android Camera系列文章目录索引汇总
  2. Android CameraX 综述
  3. 官方Demo传送门
  4. CameraX库发版记录

二、源码分析

2.1 build.gradle

  • Kotlin_version:androidx.core:core-ktx:1.6.0
  • camerax_version:1.1.0-alpha07

注意:

  1. 官方给的Demo已经很久没有更新过了,使用的camerax版本相对较老,请访问CameraX库发板记录获取最新代码。
  2. CameraX相关库大概每一个月更新一个版本。需定期及时关注并更新相应库。
  3. 目前本人使用CameraX最新的库即:
    kotlin: 1.6.10
    camerax : 1.2.0-alpha03

相关库的配置请参考官方或者我提供的对应版本。

2.2 代码结构


代码结构很简单,核心类在CameraFragment,其他可自己根据兴趣查看。

2.3 变量


CameraFragment里重要参数如下:

变量 说明
lensFacing 相机ID
preview 预览画面
Image capture 用于拍照处理
Image Analyzer 用于照片分析处理
camera 相机实例
cameraProvider 用于提供消息实例

2.3.1 lensFacing

该变量很好理解,为0或者1,分别对应如下


/** A camera on the device facing the same direction as the device's screen. */
public static final int LENS_FACING_FRONT = 0;
/** A camera on the device facing the opposite direction as the device's screen. */
public static final int LENS_FACING_BACK = 1;

于此同时也提供了相应的方法,来判断手机设备是否支持前置或者后置摄像头:

/** Returns true if the device has an available back camera. False otherwise */
private fun hasBackCamera(): Boolean {return cameraProvider?.hasCamera(CameraSelector.DEFAULT_BACK_CAMERA) ?: false
}/** Returns true if the device has an available front camera. False otherwise */
private fun hasFrontCamera(): Boolean {return cameraProvider?.hasCamera(CameraSelector.DEFAULT_FRONT_CAMERA) ?: false
}

2.3.2 preview

preview的类型为Preview。继承UseCase,这里只要了解该变量提供了Camera预览画面以及相关的操作方法,具体的阐述如下,具体的源码细节在后续相关文章中再逐步分析。
A use case that provides a camera preview stream for displaying on-screen.

2.3.3 Image capture

preview一样,Image capture也继承于UseCase,主要用于处理拍照相关的操作设置。

  1. A use case for taking a picture.
  2. This class is designed for basic picture taking.
  3. It provides takePicture() functions to take a picture to memory or save to a file, and provides image metadata.

2.3.4 Image Analyzer

继承于UseCase,用于做图片分析功能的类,平时基本用不到

A use case providing CPU accessible images for an app to perform image analysis on.

2.3.5 camera

参看源码使用就好,用到的地方不多

The camera interface is used to control the flow of data to use cases, control the camera via the CameraControl, and publish the state of the camera via CameraInfo.

2.3.6 cameraProvider

A singleton which can be used to bind the lifecycle of cameras to any LifecycleOwner within an application’s process.

2.4 初始化流程

有一部分代码,作为变量的初始化,或者更改一些UI界面,线程切换,监听手机方向,按键广播等,这块每个APP有不同的实现方案,就不具体阐述了,感兴趣的可自行分析。核心的相机初始化流程氛围如下几部分:

  1. 获取cameraProvider实例【单例】
  2. 判断并设置lensFacing
  3. 实力化preview和imageCapture, ImageAnalyzer【可选】
  4. 生成camera实例

1,2步骤在方法setUpCamera;3,4部分在bindCameraUseCases

//部分1
val cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext())
cameraProviderFuture.addListener(Runnable {cameraProvider = cameraProviderFuture.get()//部分2判断并设置lensFacinglensFacing = when {hasBackCamera() -> CameraSelector.LENS_FACING_BACKhasFrontCamera() -> CameraSelector.LENS_FACING_FRONTelse -> throw IllegalStateException("Back and front camera are unavailable")}//部分3 实力化preview和imageCapture, ImageAnalyzer【可选】//部分4 生成camera实例bindCameraUseCases()
}, ContextCompat.getMainExecutor(requireContext()))
private fun bindCameraUseCases(){//*********部分三:实力化preview和imageCapture, ImageAnalyzer***********// CameraSelectorval cameraSelector = CameraSelector.Builder().requireLensFacing(lensFacing).build()// 实例化Previewpreview = Preview.Builder()// We request aspect ratio but no resolution//设置屏幕宽高比.setTargetAspectRatio(screenAspectRatio)// Set initial target rotation//设置屏幕方向.setTargetRotation(rotation).build()// 实例化ImageCaptureimageCapture = ImageCapture.Builder()//设置拍照模式为延迟优先.setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)// We request aspect ratio but no resolution to match preview config, but letting// CameraX optimize for whatever specific resolution best fits our use cases//设置宽高比.setTargetAspectRatio(screenAspectRatio)// Set initial target rotation, we will have to call this again if rotation changes// during the lifecycle of this use case.setTargetRotation(rotation).build()// ImageAnalysisimageAnalyzer = ImageAnalysis.Builder()// We request aspect ratio but no resolution.setTargetAspectRatio(screenAspectRatio)// Set initial target rotation, we will have to call this again if rotation changes// during the lifecycle of this use case.setTargetRotation(rotation).build()// The analyzer can then be assigned to the instance.also {it.setAnalyzer(cameraExecutor, LuminosityAnalyzer { luma ->// Values returned from our analyzer are passed to the attached listener// We log image analysis results here - you should do something useful// instead!Log.d(TAG, "Average luminosity: $luma")})}//*********部分4:生成camera实例***********cameraProvider.unbindAll()try {// A variable number of use-cases can be passed here -// camera provides access to CameraControl & CameraInfocamera = cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture, imageAnalyzer)// Attach the viewfinder's surface provider to preview use casepreview?.setSurfaceProvider(fragmentCameraBinding.viewFinder.surfaceProvider)} catch (exc: Exception) {Log.e(TAG, "Use case binding failed", exc)}}

2.5 拍照

使用Image Capture

cameraUiContainerBinding?.cameraCaptureButton?.setOnClickListener {// Get a stable reference of the modifiable image capture use caseimageCapture?.let { imageCapture ->******1.获取拍照输出文件******// Create output file to hold the imageval photoFile = createFile(outputDirectory, FILENAME, PHOTO_EXTENSION)// Setup image capture metadataval metadata = Metadata().apply {// Mirror image when using the front cameraisReversedHorizontal = lensFacing == CameraSelector.LENS_FACING_FRONT}******2.配置ImageCapture输出选项******// Create output options object which contains file + metadataval outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).setMetadata(metadata).build()// Setup image capture listener which is triggered after photo has been taken******3.调用takePicture方法******imageCapture.takePicture(outputOptions, cameraExecutor, object : ImageCapture.OnImageSavedCallback {override fun onError(exc: ImageCaptureException) {Log.e(TAG, "Photo capture failed: ${exc.message}", exc)}override fun onImageSaved(output: ImageCapture.OutputFileResults) {******4.回调******val savedUri = output.savedUri ?: Uri.fromFile(photoFile)Log.d(TAG, "Photo capture succeeded: $savedUri")if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {requireActivity().sendBroadcast(Intent(android.hardware.Camera.ACTION_NEW_PICTURE, savedUri))}})}
}

2.6 Release

释放Camera实例方法很简单:

cameraProviderFuture.get().unbindAll();

三、相关练习

  1. git clone官方源码分析其他相关的类
  2. 自己参考官方源码完成一个CameraX实例Demo的实现。

附录 CameraFragment源码:

/** Copyright 2020 The Android Open Source Project** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**     https://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package com.android.example.cameraxbasic.fragmentsimport android.annotation.SuppressLint
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.res.Configuration
import android.graphics.Color
import android.graphics.ImageFormat
import android.graphics.drawable.ColorDrawable
import android.hardware.display.DisplayManager
import android.media.MediaScannerConnection
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.view.*
import android.webkit.MimeTypeMap
import androidx.camera.core.*
import androidx.camera.core.ImageCapture.FLASH_MODE_ON
import androidx.camera.core.ImageCapture.Metadata
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.core.content.ContextCompat
import androidx.core.net.toFile
import androidx.core.view.setPadding
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.navigation.Navigation
import androidx.window.WindowManager
import com.android.example.cameraxbasic.KEY_EVENT_ACTION
import com.android.example.cameraxbasic.KEY_EVENT_EXTRA
import com.android.example.cameraxbasic.MainActivity
import com.android.example.cameraxbasic.R
import com.android.example.cameraxbasic.databinding.CameraUiContainerBinding
import com.android.example.cameraxbasic.databinding.FragmentCameraBinding
import com.android.example.cameraxbasic.utils.ANIMATION_FAST_MILLIS
import com.android.example.cameraxbasic.utils.ANIMATION_SLOW_MILLIS
import com.android.example.cameraxbasic.utils.simulateClick
import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.File
import java.nio.ByteBuffer
import java.text.SimpleDateFormat
import java.util.ArrayDeque
import java.util.Locale
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import kotlin.collections.ArrayList
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min/** Helper type alias used for analysis use case callbacks */
typealias LumaListener = (luma: Double) -> Unit/*** Main fragment for this app. Implements all camera operations including:* - Viewfinder* - Photo taking* - Image analysis*/
class CameraFragment : Fragment() {private var _fragmentCameraBinding: FragmentCameraBinding? = nullprivate val fragmentCameraBinding get() = _fragmentCameraBinding!!private var cameraUiContainerBinding: CameraUiContainerBinding? = nullprivate lateinit var outputDirectory: Fileprivate lateinit var broadcastManager: LocalBroadcastManagerprivate var displayId: Int = -1private var lensFacing: Int = CameraSelector.LENS_FACING_BACKprivate var preview: Preview? = nullprivate var imageCapture: ImageCapture? = nullprivate var imageAnalyzer: ImageAnalysis? = nullprivate var camera: Camera? = nullprivate var cameraProvider: ProcessCameraProvider? = nullprivate lateinit var windowManager: WindowManagerprivate val displayManager by lazy {requireContext().getSystemService(Context.DISPLAY_SERVICE) as DisplayManager}/** Blocking camera operations are performed using this executor */private lateinit var cameraExecutor: ExecutorService/** Volume down button receiver used to trigger shutter */private val volumeDownReceiver = object : BroadcastReceiver() {override fun onReceive(context: Context, intent: Intent) {when (intent.getIntExtra(KEY_EVENT_EXTRA, KeyEvent.KEYCODE_UNKNOWN)) {// When the volume down button is pressed, simulate a shutter button clickKeyEvent.KEYCODE_VOLUME_DOWN -> {cameraUiContainerBinding?.cameraCaptureButton?.simulateClick()}}}}/*** We need a display listener for orientation changes that do not trigger a configuration* change, for example if we choose to override config change in manifest or for 180-degree* orientation changes.*/private val displayListener = object : DisplayManager.DisplayListener {override fun onDisplayAdded(displayId: Int) = Unitoverride fun onDisplayRemoved(displayId: Int) = Unitoverride fun onDisplayChanged(displayId: Int) = view?.let { view ->if (displayId == this@CameraFragment.displayId) {Log.d(TAG, "Rotation changed: ${view.display.rotation}")imageCapture?.targetRotation = view.display.rotationimageAnalyzer?.targetRotation = view.display.rotation}} ?: Unit}override fun onResume() {super.onResume()// Make sure that all permissions are still present, since the// user could have removed them while the app was in paused state.if (!PermissionsFragment.hasPermissions(requireContext())) {Navigation.findNavController(requireActivity(), R.id.fragment_container).navigate(CameraFragmentDirections.actionCameraToPermissions())}}override fun onDestroyView() {_fragmentCameraBinding = nullsuper.onDestroyView()// Shut down our background executorcameraExecutor.shutdown()// Unregister the broadcast receivers and listenersbroadcastManager.unregisterReceiver(volumeDownReceiver)displayManager.unregisterDisplayListener(displayListener)}override fun onCreateView(inflater: LayoutInflater,container: ViewGroup?,savedInstanceState: Bundle?): View {_fragmentCameraBinding = FragmentCameraBinding.inflate(inflater, container, false)return fragmentCameraBinding.root}private fun setGalleryThumbnail(uri: Uri) {// Run the operations in the view's threadcameraUiContainerBinding?.photoViewButton?.let { photoViewButton ->photoViewButton.post {// Remove thumbnail paddingphotoViewButton.setPadding(resources.getDimension(R.dimen.stroke_small).toInt())// Load thumbnail into circular button using GlideGlide.with(photoViewButton).load(uri).apply(RequestOptions.circleCropTransform()).into(photoViewButton)}}}@SuppressLint("MissingPermission")override fun onViewCreated(view: View, savedInstanceState: Bundle?) {super.onViewCreated(view, savedInstanceState)// Initialize our background executorcameraExecutor = Executors.newSingleThreadExecutor()broadcastManager = LocalBroadcastManager.getInstance(view.context)// Set up the intent filter that will receive events from our main activityval filter = IntentFilter().apply { addAction(KEY_EVENT_ACTION) }broadcastManager.registerReceiver(volumeDownReceiver, filter)// Every time the orientation of device changes, update rotation for use casesdisplayManager.registerDisplayListener(displayListener, null)//Initialize WindowManager to retrieve display metricswindowManager = WindowManager(view.context)// Determine the output directoryoutputDirectory = MainActivity.getOutputDirectory(requireContext())// Wait for the views to be properly laid outfragmentCameraBinding.viewFinder.post {// Keep track of the display in which this view is attacheddisplayId = fragmentCameraBinding.viewFinder.display.displayId// Build UI controlsupdateCameraUi()// Set up the camera and its use casessetUpCamera()}fragmentCameraBinding.viewFinder.setOnTouchListener { v, event ->if (event.action != MotionEvent.ACTION_UP) {return@setOnTouchListener true}val factory = fragmentCameraBinding.viewFinder.meteringPointFactory;val focusPoint = factory.createPoint(event.x, event.y,0.3f)Log.i("sun_q","point = "+focusPoint.toString())val focusAction = FocusMeteringAction.Builder(focusPoint).build()camera?.cameraControl?.startFocusAndMetering(focusAction)return@setOnTouchListener true}}/*** Inflate camera controls and update the UI manually upon config changes to avoid removing* and re-adding the view finder from the view hierarchy; this provides a seamless rotation* transition on devices that support it.** NOTE: The flag is supported starting in Android 8 but there still is a small flash on the* screen for devices that run Android 9 or below.*/override fun onConfigurationChanged(newConfig: Configuration) {super.onConfigurationChanged(newConfig)// Rebind the camera with the updated display metricsbindCameraUseCases()// Enable or disable switching between camerasupdateCameraSwitchButton()}/** Initialize CameraX, and prepare to bind the camera use cases  */private fun setUpCamera() {val cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext())cameraProviderFuture.addListener(Runnable {// CameraProvidercameraProvider = cameraProviderFuture.get()// Select lensFacing depending on the available cameraslensFacing = when {hasBackCamera() -> CameraSelector.LENS_FACING_BACKhasFrontCamera() -> CameraSelector.LENS_FACING_FRONTelse -> throw IllegalStateException("Back and front camera are unavailable")}// Enable or disable switching between camerasupdateCameraSwitchButton()// Build and bind the camera use casesbindCameraUseCases()}, ContextCompat.getMainExecutor(requireContext()))}/** Declare and bind preview, capture and analysis use cases */private fun bindCameraUseCases() {// Get screen metrics used to setup camera for full screen resolutionval metrics = windowManager.getCurrentWindowMetrics().boundsLog.d(TAG, "Screen metrics: ${metrics.width()} x ${metrics.height()}")val screenAspectRatio = aspectRatio(metrics.width(), metrics.height())Log.d(TAG, "Preview aspect ratio: $screenAspectRatio")val rotation = fragmentCameraBinding.viewFinder.display.rotation// CameraProviderval cameraProvider = cameraProvider?: throw IllegalStateException("Camera initialization failed.")// CameraSelectorval cameraSelector = CameraSelector.Builder().requireLensFacing(lensFacing).build()// Previewpreview = Preview.Builder()// We request aspect ratio but no resolution.setTargetAspectRatio(screenAspectRatio)// Set initial target rotation.setTargetRotation(rotation).build()// ImageCaptureimageCapture = ImageCapture.Builder().setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)// We request aspect ratio but no resolution to match preview config, but letting// CameraX optimize for whatever specific resolution best fits our use cases.setTargetAspectRatio(screenAspectRatio)// Set initial target rotation, we will have to call this again if rotation changes// during the lifecycle of this use case.setTargetRotation(rotation).build()// ImageAnalysisimageAnalyzer = ImageAnalysis.Builder()// We request aspect ratio but no resolution.setTargetAspectRatio(screenAspectRatio)// Set initial target rotation, we will have to call this again if rotation changes// during the lifecycle of this use case.setTargetRotation(rotation).build()// The analyzer can then be assigned to the instance.also {it.setAnalyzer(cameraExecutor, LuminosityAnalyzer { luma ->// Values returned from our analyzer are passed to the attached listener// We log image analysis results here - you should do something useful// instead!Log.d(TAG, "Average luminosity: $luma")})}// Must unbind the use-cases before rebinding themcameraProvider.unbindAll()try {// A variable number of use-cases can be passed here -// camera provides access to CameraControl & CameraInfocamera = cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture, imageAnalyzer)// Attach the viewfinder's surface provider to preview use casepreview?.setSurfaceProvider(fragmentCameraBinding.viewFinder.surfaceProvider)} catch (exc: Exception) {Log.e(TAG, "Use case binding failed", exc)}}/***  [androidx.camera.core.ImageAnalysis.Builder] requires enum value of*  [androidx.camera.core.AspectRatio]. Currently it has values of 4:3 & 16:9.**  Detecting the most suitable ratio for dimensions provided in @params by counting absolute*  of preview ratio to one of the provided values.**  @param width - preview width*  @param height - preview height*  @return suitable aspect ratio*/private fun aspectRatio(width: Int, height: Int): Int {val previewRatio = max(width, height).toDouble() / min(width, height)if (abs(previewRatio - RATIO_4_3_VALUE) <= abs(previewRatio - RATIO_16_9_VALUE)) {return AspectRatio.RATIO_4_3}return AspectRatio.RATIO_16_9}/** Method used to re-draw the camera UI controls, called every time configuration changes. */private fun updateCameraUi() {// Remove previous UI if anycameraUiContainerBinding?.root?.let {fragmentCameraBinding.root.removeView(it)}cameraUiContainerBinding = CameraUiContainerBinding.inflate(LayoutInflater.from(requireContext()),fragmentCameraBinding.root,true)// In the background, load latest photo taken (if any) for gallery thumbnaillifecycleScope.launch(Dispatchers.IO) {outputDirectory.listFiles { file ->EXTENSION_WHITELIST.contains(file.extension.toUpperCase(Locale.ROOT))}?.maxOrNull()?.let {setGalleryThumbnail(Uri.fromFile(it))}}// Listener for button used to capture photocameraUiContainerBinding?.cameraCaptureButton?.setOnClickListener {// Get a stable reference of the modifiable image capture use caseimageCapture?.let { imageCapture ->// Create output file to hold the imageval photoFile = createFile(outputDirectory, FILENAME, PHOTO_EXTENSION)// Setup image capture metadataval metadata = Metadata().apply {// Mirror image when using the front cameraisReversedHorizontal = lensFacing == CameraSelector.LENS_FACING_FRONT}// Create output options object which contains file + metadataval outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).setMetadata(metadata).build()// Setup image capture listener which is triggered after photo has been takenimageCapture.takePicture(outputOptions, cameraExecutor, object : ImageCapture.OnImageSavedCallback {override fun onError(exc: ImageCaptureException) {Log.e(TAG, "Photo capture failed: ${exc.message}", exc)}override fun onImageSaved(output: ImageCapture.OutputFileResults) {val savedUri = output.savedUri ?: Uri.fromFile(photoFile)Log.d(TAG, "Photo capture succeeded: $savedUri")// We can only change the foreground Drawable using API level 23+ APIif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {// Update the gallery thumbnail with latest picture takensetGalleryThumbnail(savedUri)}// Implicit broadcasts will be ignored for devices running API level >= 24// so if you only target API level 24+ you can remove this statementif (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {requireActivity().sendBroadcast(Intent(android.hardware.Camera.ACTION_NEW_PICTURE, savedUri))}// If the folder selected is an external media directory, this is// unnecessary but otherwise other apps will not be able to access our// images unless we scan them using [MediaScannerConnection]val mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(savedUri.toFile().extension)MediaScannerConnection.scanFile(context,arrayOf(savedUri.toFile().absolutePath),arrayOf(mimeType)) { _, uri ->Log.d(TAG, "Image capture scanned into media store: $uri")}}})// We can only change the foreground Drawable using API level 23+ APIif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {// Display flash animation to indicate that photo was capturedfragmentCameraBinding.root.postDelayed({fragmentCameraBinding.root.foreground = ColorDrawable(Color.WHITE)fragmentCameraBinding.root.postDelayed({ fragmentCameraBinding.root.foreground = null }, ANIMATION_FAST_MILLIS)}, ANIMATION_SLOW_MILLIS)}}}// Setup for button used to switch camerascameraUiContainerBinding?.cameraSwitchButton?.let {// Disable the button until the camera is set upit.isEnabled = false// Listener for button used to switch cameras. Only called if the button is enabledit.setOnClickListener {lensFacing = if (CameraSelector.LENS_FACING_FRONT == lensFacing) {CameraSelector.LENS_FACING_BACK} else {CameraSelector.LENS_FACING_FRONT}// Re-bind use cases to update selected camerabindCameraUseCases()}}// Listener for button used to view the most recent photocameraUiContainerBinding?.photoViewButton?.setOnClickListener {// Only navigate when the gallery has photosif (true == outputDirectory.listFiles()?.isNotEmpty()) {Navigation.findNavController(requireActivity(), R.id.fragment_container).navigate(CameraFragmentDirections.actionCameraToGallery(outputDirectory.absolutePath))}}}/** Enabled or disabled a button to switch cameras depending on the available cameras */private fun updateCameraSwitchButton() {try {cameraUiContainerBinding?.cameraSwitchButton?.isEnabled = hasBackCamera() && hasFrontCamera()} catch (exception: CameraInfoUnavailableException) {cameraUiContainerBinding?.cameraSwitchButton?.isEnabled = false}}/** Returns true if the device has an available back camera. False otherwise */private fun hasBackCamera(): Boolean {return cameraProvider?.hasCamera(CameraSelector.DEFAULT_BACK_CAMERA) ?: false}/** Returns true if the device has an available front camera. False otherwise */private fun hasFrontCamera(): Boolean {return cameraProvider?.hasCamera(CameraSelector.DEFAULT_FRONT_CAMERA) ?: false}/*** Our custom image analysis class.** <p>All we need to do is override the function `analyze` with our desired operations. Here,* we compute the average luminosity of the image by looking at the Y plane of the YUV frame.*/private class LuminosityAnalyzer(listener: LumaListener? = null) : ImageAnalysis.Analyzer {private val frameRateWindow = 8private val frameTimestamps = ArrayDeque<Long>(5)private val listeners = ArrayList<LumaListener>().apply { listener?.let { add(it) } }private var lastAnalyzedTimestamp = 0Lvar framesPerSecond: Double = -1.0private set/*** Used to add listeners that will be called with each luma computed*/fun onFrameAnalyzed(listener: LumaListener) = listeners.add(listener)/*** Helper extension function used to extract a byte array from an image plane buffer*/private fun ByteBuffer.toByteArray(): ByteArray {rewind()    // Rewind the buffer to zeroval data = ByteArray(remaining())get(data)   // Copy the buffer into a byte arrayreturn data // Return the byte array}/*** Analyzes an image to produce a result.** <p>The caller is responsible for ensuring this analysis method can be executed quickly* enough to prevent stalls in the image acquisition pipeline. Otherwise, newly available* images will not be acquired and analyzed.** <p>The image passed to this method becomes invalid after this method returns. The caller* should not store external references to this image, as these references will become* invalid.** @param image image being analyzed VERY IMPORTANT: Analyzer method implementation must* call image.close() on received images when finished using them. Otherwise, new images* may not be received or the camera may stall, depending on back pressure setting.**/override fun analyze(image: ImageProxy) {// If there are no listeners attached, we don't need to perform analysisif (listeners.isEmpty()) {image.close()return}// Keep track of frames analyzedval currentTime = System.currentTimeMillis()frameTimestamps.push(currentTime)// Compute the FPS using a moving averagewhile (frameTimestamps.size >= frameRateWindow) frameTimestamps.removeLast()val timestampFirst = frameTimestamps.peekFirst() ?: currentTimeval timestampLast = frameTimestamps.peekLast() ?: currentTimeframesPerSecond = 1.0 / ((timestampFirst - timestampLast) /frameTimestamps.size.coerceAtLeast(1).toDouble()) * 1000.0// Analysis could take an arbitrarily long amount of time// Since we are running in a different thread, it won't stall other use caseslastAnalyzedTimestamp = frameTimestamps.first// Since format in ImageAnalysis is YUV, image.planes[0] contains the luminance planeval buffer = image.planes[0].buffer// Extract image data from callback objectval data = buffer.toByteArray()// Convert the data into an array of pixel values ranging 0-255val pixels = data.map { it.toInt() and 0xFF }// Compute average luminance for the imageval luma = pixels.average()// Call all listeners with new valuelisteners.forEach { it(luma) }image.close()}}companion object {private const val TAG = "CameraXBasic"private const val FILENAME = "yyyy-MM-dd-HH-mm-ss-SSS"private const val PHOTO_EXTENSION = ".jpg"private const val RATIO_4_3_VALUE = 4.0 / 3.0private const val RATIO_16_9_VALUE = 16.0 / 9.0/** Helper function used to create a timestamped file */private fun createFile(baseFolder: File, format: String, extension: String) =File(baseFolder, SimpleDateFormat(format, Locale.US).format(System.currentTimeMillis()) + extension)}
}

【Android CameraX】CameraXBasic —— 官方CameraX实例源码分析相关推荐

  1. Android上百实例源码分析以及开源分析集合打包

    感谢网友banketree的收集,压缩包的内容如下: 1.360新版特性界面源代码 实现了360新版特性界面的效果,主要涉及到Qt的一些事件处理与自定义控件.但源码好像是c++. 2.aidl跨进程调 ...

  2. 【Android 启动过程】Activity 启动源码分析 ( ActivityThread -> Activity、主线程阶段 二 )

    文章目录 前言 一.ActivityThread 类 handleLaunchActivity -> performLaunchActivity 方法 二.Instrumentation.new ...

  3. 【Android 启动过程】Activity 启动源码分析 ( AMS -> ActivityThread、AMS 线程阶段 二 )

    文章目录 前言 一.热启动与冷启动选择 二.AMS 进程中执行的相关操作 三.通过 Binder 机制转到 ActivityThread 中执行的操作 总结 前言 上一篇博客 [Android 启动过 ...

  4. 【Android 启动过程】Activity 启动源码分析 ( ActivityThread 流程分析 二 )

    文章目录 前言 一.ActivityManagerService.attachApplicationLocked 二.ActivityStackSupervisor.attachApplication ...

  5. 【Android 启动过程】Activity 启动源码分析 ( ActivityThread -> Activity、主线程阶段 一 )

    文章目录 前言 一.ClientTransactionHandler.scheduleTransaction 二.ActivityThread.H 处理 EXECUTE_TRANSACTION 消息 ...

  6. 【Android 电量优化】JobScheduler 相关源码分析 ( JobSchedulerService 源码分析 | 任务检查 | 任务执行 )

    文章目录 一.回调 StateChangedListener 接口 二.JobHandler 处理 ( 任务检查 ) 三.maybeRunPendingJobsH 方法 四.assignJobsToC ...

  7. Android 9 (P) Zygote进程启动源码分析指南二

         Android 9 Zygote进程启动源码分析指南二 Android 9 (P) 系统启动及进程创建源码分析目录: Android 9 (P)之init进程启动源码分析指南之一 Andro ...

  8. Android+上百实例源码分析以及开源分析+集合打包

    1.360新版特性界面源代码 实现了360新版特性界面的效果,主要涉及到Qt的一些事件处理与自定义控件.但源码好像是c++. 2.aidl跨进程调用 服务端onBind暴露,然后客户端bindServ ...

  9. android字符显示流程图,Android应用层View绘制流程与源码分析

    1  背景 还记得前面<Android应用setContentView与LayoutInflater加载解析机制源码分析>这篇文章吗?我们有分析到Activity中界面加载显示的基本流程原 ...

最新文章

  1. Python中eval函数的表达式如何使用
  2. HttpClient, 使用C#操作Web
  3. iis php性能优化,IIS 6 的 PHP 最佳配置方法
  4. Linux学习总结(19)——Linux中文本编辑器vim特殊使用方法
  5. Deepin添加PPA显示没有公钥签名
  6. 应用系统运行监控界面_重庆悦来会展二期电力监控系统的设计与应用
  7. VistaNet: Visual Aspect Attention Network for Multimodal Sentiment Analysis 论文笔记
  8. 局域网 访问计算机 软件,局域网共享软件
  9. 后台审批功能 销售发货单 生成 销售出库单 java NC633 接口开发
  10. 软考——论文写作基本介绍
  11. php_curl-5.4.3-VC9-x64下php_curl.dll加载出错
  12. 计算机二级vb语言题库百度云,计算机二级VB语言程序设计考试题及答案
  13. CAJ阅读器相关问题
  14. Android Camera 预览及录制视频 附demo
  15. 本安计算机电缆执行标准,阻燃本安计算机信号电缆ZR-IA-DJYPVRP-1*2*1.5
  16. Android 获取 OAID ,替换 IMEI (兼容 Android 10获取IMEI问题)
  17. Mac无缝更换电脑--数据迁移
  18. c语言程序设计开发出怎样包装设计,设计开发输出可以是()。A.图纸B.计算书C.包装规范D.以上都是...
  19. 苹果Mac系统偏好设置怎样原封不动地迁移(导入)到新电脑中?
  20. 跟siki学院教程学习愤怒的小鸟案例猪多次碰撞解决记录<一>

热门文章

  1. 神经网络体系搭建(五)
  2. U盘无法弹出的有效解决方法
  3. 智力注意力训练亲子游戏
  4. 计算机改名字后找不到网络,改了wifi名字后电脑搜不到网络怎么办? | 192路由网...
  5. 删除 Windows Subsystem for Linux (WSL)
  6. MySQL入门笔记1
  7. SpringBoot入门篇———快速开始你的第一个SpringBoot应用
  8. TimesTen 应用层数据库缓存学习:8. 配置Sliding Window(滑动窗口)
  9. 一级计算机windows考试试题,全国计算机等级考试一级笔试试题Windows(2)
  10. php仿小红书,Android仿小红书图片标签