Android CameraX的基础使用

这篇具有很好参考价值的文章主要介绍了Android CameraX的基础使用。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

本博客介绍CamaraX的基本使用,主要基于Android官方教程。CameraX是一个 Jetpack 库,能够快速的开发相机应用,并且有良好兼容性。CameraX支持最低API level 21,并且本教程要求Android Studio Arctic Fox 2020.3.1 或更高版本。

创建项目

首先创建一个新项目,选择“Empty Activity”。
Android CameraX的基础使用
将项目命名为“CameraXApp”,软件包名称更改为“com.android.example.cameraxapp”。选择Kotlin语言开发,设定最低支持的API Level 21(CameraX 所需的最低级别)。
Android CameraX的基础使用

添加 Gradle 依赖

打开项目的模块(Module)的build.gradle 文件,并添加 CameraX 依赖项:

dependencies {
  def camerax_version = "1.1.0-beta01"
  implementation "androidx.camera:camera-core:${camerax_version}"
  implementation "androidx.camera:camera-camera2:${camerax_version}"
  implementation "androidx.camera:camera-lifecycle:${camerax_version}"
  implementation "androidx.camera:camera-video:${camerax_version}"

  implementation "androidx.camera:camera-view:${camerax_version}"
  implementation "androidx.camera:camera-extensions:${camerax_version}"
}

CameraX 需要一些属于 Java 8 的方法,因此需要相应地设置编译选项(实际上比较新的Android Studio版本会默认设置)。在 android 代码块的末尾,紧跟在 buildTypes 之后,添加以下代码:

compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
}

因为在项目中使用了ViewBinding,在 android{} 代码块末尾添加如下代码:

buildFeatures {
   viewBinding true
}

左侧出现提示时(灯泡状),点击 Sync Now,于是便可以在应用中使用 CameraX 了。

创建项目布局

本项目中,涉及以下的功能:

  • CameraX PreviewView(用于预览相机图片/视频)。
  • 用于控制图片拍摄的标准按钮。
  • 用于开始/停止视频拍摄的标准按钮。
  • 用于放置 2 个按钮的垂直指南。

打开res/layout/activity_main.xml 的 activity_main 布局文件,并将其替换为以下代码。

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   tools:context=".MainActivity">

   <androidx.camera.view.PreviewView
       android:id="@+id/viewFinder"
       android:layout_width="match_parent"
       android:layout_height="match_parent" />

   <Button
       android:id="@+id/image_capture_button"
       android:layout_width="110dp"
       android:layout_height="110dp"
       android:layout_marginBottom="50dp"
       android:layout_marginEnd="50dp"
       android:elevation="2dp"
       android:text="@string/take_photo"
       app:layout_constraintBottom_toBottomOf="parent"
       app:layout_constraintLeft_toLeftOf="parent"
       app:layout_constraintEnd_toStartOf="@id/vertical_centerline" />

   <Button
       android:id="@+id/video_capture_button"
       android:layout_width="110dp"
       android:layout_height="110dp"
       android:layout_marginBottom="50dp"
       android:layout_marginStart="50dp"
       android:elevation="2dp"
       android:text="@string/start_capture"
       app:layout_constraintBottom_toBottomOf="parent"
       app:layout_constraintStart_toEndOf="@id/vertical_centerline" />

   <androidx.constraintlayout.widget.Guideline
       android:id="@+id/vertical_centerline"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:orientation="vertical"
       app:layout_constraintGuide_percent=".50" />

</androidx.constraintlayout.widget.ConstraintLayout>

更新res/values/strings.xml 文件

<resources>
   <string name="app_name">CameraXApp</string>
   <string name="take_photo">Take Photo</string>
   <string name="start_capture">Start Capture</string>
   <string name="stop_capture">Stop Capture</string>
</resources>

布局界面效果如图
Android CameraX的基础使用

编写 MainActivity.kt 代码

将 MainActivity.kt 中的代码替换为以下代码,但保留软件包名称不变。它包含 import 语句、将要实例化的变量、要实现的函数以及常量。

系统已实现 onCreate(),供我们检查相机权限、启动相机、为照片和拍摄按钮设置 onClickListener(),以及实现 cameraExecutor。虽然系统已经实现 onCreate(),但在实现文件中的方法之前,相机将无法正常工作。

package com.android.example.cameraxapp

import android.Manifest
import android.content.ContentValues
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.provider.MediaStore
import androidx.appcompat.app.AppCompatActivity
import androidx.camera.core.ImageCapture
import androidx.camera.video.Recorder
import androidx.camera.video.Recording
import androidx.camera.video.VideoCapture
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.android.example.cameraxapp.databinding.ActivityMainBinding
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import android.widget.Toast
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.core.Preview
import androidx.camera.core.CameraSelector
import android.util.Log
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageCaptureException
import androidx.camera.core.ImageProxy
import androidx.camera.video.FallbackStrategy
import androidx.camera.video.MediaStoreOutputOptions
import androidx.camera.video.Quality
import androidx.camera.video.QualitySelector
import androidx.camera.video.VideoRecordEvent
import androidx.core.content.PermissionChecker
import java.nio.ByteBuffer
import java.text.SimpleDateFormat
import java.util.Locale

typealias LumaListener = (luma: Double) -> Unit

class MainActivity : AppCompatActivity() {
   private lateinit var viewBinding: ActivityMainBinding

   private var imageCapture: ImageCapture? = null

   private var videoCapture: VideoCapture<Recorder>? = null
   private var recording: Recording? = null

   private lateinit var cameraExecutor: ExecutorService

   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       viewBinding = ActivityMainBinding.inflate(layoutInflater)
       setContentView(viewBinding.root)

       // Request camera permissions
       if (allPermissionsGranted()) {
           startCamera()
       } else {
           ActivityCompat.requestPermissions(
               this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS)
       }

       // Set up the listeners for take photo and video capture buttons
       viewBinding.imageCaptureButton.setOnClickListener { takePhoto() }
       viewBinding.videoCaptureButton.setOnClickListener { captureVideo() }

       cameraExecutor = Executors.newSingleThreadExecutor()
   }

   private fun takePhoto() {}

   private fun captureVideo() {}

   private fun startCamera() {}

   private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
       ContextCompat.checkSelfPermission(
           baseContext, it) == PackageManager.PERMISSION_GRANTED
   }

   override fun onDestroy() {
       super.onDestroy()
       cameraExecutor.shutdown()
   }

   companion object {
       private const val TAG = "CameraXApp"
       private const val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS"
       private const val REQUEST_CODE_PERMISSIONS = 10
       private val REQUIRED_PERMISSIONS =
           mutableListOf (
               Manifest.permission.CAMERA,
               Manifest.permission.RECORD_AUDIO
           ).apply {
               if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
                   add(Manifest.permission.WRITE_EXTERNAL_STORAGE)
               }
           }.toTypedArray()
   }
}

请求必要的权限

应用需要获得用户授权才能打开相机;录制音频也需要麦克风权限;在 Android 9 § 及更低版本上,MediaStore 需要外部存储空间写入权限。在此步骤中,我们将实现这些必要的权限。

打开 AndroidManifest.xml,然后将以下代码行添加到 application 标记之前。

<uses-feature android:name="android.hardware.camera.any" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
   android:maxSdkVersion="28" />

添加 android.hardware.camera.any 可确保设备配有相机。指定 .any 表示它可以是前置摄像头,也可以是后置摄像头。

如果在未指定 .any 的情况下使用 android.hardware.camera,并且设备未配有后置摄像头(例如大多数 Chromebook),那么相机将无法正常运行。第二行代码会添加对该相机的使用权限。

然后,复制代码到MainActivity.kt. 中。

override fun onRequestPermissionsResult(
   requestCode: Int, permissions: Array<String>, grantResults:
   IntArray) {
   if (requestCode == REQUEST_CODE_PERMISSIONS) {
       if (allPermissionsGranted()) {
           startCamera()
       } else {
           Toast.makeText(this,
               "Permissions not granted by the user.",
               Toast.LENGTH_SHORT).show()
           finish()
       }
   }
}

其中代码

if (requestCode == REQUEST_CODE_PERMISSIONS) {

}

检查请求代码是否正确;否则,忽略它。代码

if (allPermissionsGranted()) {
   startCamera()
}

判断是否已授予权限,若是则调用 startCamera() (进行预览);如果未授予权限,系统会显示一个消息框,通知用户未授予权限。

else {
   Toast.makeText(this,
       "Permissions not granted by the user.",
       Toast.LENGTH_SHORT).show()
   finish()
}

运行应用,可发现应用程序请求使用摄像头和麦克风:
Android CameraX的基础使用

实现 Preview 用例

在相机应用中,取景器用于让用户预览他们拍摄的照片。我们将使用 CameraX Preview 类实现取景器。

如需使用 Preview,首先需要定义一个配置,然后系统会使用该配置创建用例的实例。生成的实例就是绑定到 CameraX 生命周期的内容。填充之前的startCamera() 函数

private fun startCamera() {
   val cameraProviderFuture = ProcessCameraProvider.getInstance(this)

   cameraProviderFuture.addListener({
       // Used to bind the lifecycle of cameras to the lifecycle owner
       val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()

       // Preview
       val preview = Preview.Builder()
          .build()
          .also {
              it.setSurfaceProvider(viewBinding.viewFinder.surfaceProvider)
          }

       // Select back camera as a default
       val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA

       try {
           // Unbind use cases before rebinding
           cameraProvider.unbindAll()

           // Bind use cases to camera
           cameraProvider.bindToLifecycle(
               this, cameraSelector, preview)

       } catch(exc: Exception) {
           Log.e(TAG, "Use case binding failed", exc)
       }

   }, ContextCompat.getMainExecutor(this))
}

以下分析上述代码。创建 ProcessCameraProvider 的实例。这用于将相机的生命周期绑定到生命周期所有者。这消除了打开和关闭相机的任务,因为 CameraX 具有生命周期感知能力。

val cameraProviderFuture = ProcessCameraProvider.getInstance(this)

向 cameraProviderFuture 添加监听器。添加 Runnable 作为一个参数。添加 ContextCompat.getMainExecutor() 作为第二个参数。这将返回一个在主线程上运行的 Executor。其具体形式:

cameraProviderFuture.addListener(Runnable {}, ContextCompat.getMainExecutor(this))

在 Runnable 中,添加 ProcessCameraProvider。它用于将相机的生命周期绑定到应用进程中的 LifecycleOwner。

val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()

初始化 Preview 对象,在其上调用 build,从取景器中获取 Surface 提供程序,然后在预览上进行设置。

val preview = Preview.Builder()
   .build()
   .also {
       it.setSurfaceProvider(viewBinding.viewFinder.surfaceProvider)
   }

创建 CameraSelector 对象,然后选择 DEFAULT_BACK_CAMERA。

val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA

创建一个 try 代码块。在此块内,确保没有任何内容绑定到 cameraProvider,然后将 cameraSelector 和预览对象绑定到 cameraProvider。

try {
   cameraProvider.unbindAll()
   cameraProvider.bindToLifecycle(
       this, cameraSelector, preview)
}

有多种原因可能会导致此代码失败,例如应用不再获得焦点。将此代码封装到 catch 块中,以便在出现故障时记录日志。

catch(exc: Exception) {
      Log.e(TAG, "Use case binding failed", exc)
}

最后,运行应用,可以看到相机预览
Android CameraX的基础使用

实现 ImageCapture 用例(拍照功能)

其他用例与 Preview 非常相似。首先,定义一个配置对象,该对象用于实例化实际用例对象。若要拍摄照片,需要实现 takePhoto() 方法,该方法会在用户按下 photo 按钮时调用。填充takePhoto() 方法的代码:

private fun takePhoto() {
   // Get a stable reference of the modifiable image capture use case
   val imageCapture = imageCapture ?: return

   // Create time stamped name and MediaStore entry.
   val name = SimpleDateFormat(FILENAME_FORMAT, Locale.US)
              .format(System.currentTimeMillis())
   val contentValues = ContentValues().apply {
       put(MediaStore.MediaColumns.DISPLAY_NAME, name)
       put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
       if(Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
           put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/CameraX-Image")
       }
   }

   // Create output options object which contains file + metadata
   val outputOptions = ImageCapture.OutputFileOptions
           .Builder(contentResolver,
                    MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                    contentValues)
           .build()

   // Set up image capture listener, which is triggered after photo has
   // been taken
   imageCapture.takePicture(
       outputOptions,
       ContextCompat.getMainExecutor(this),
       object : ImageCapture.OnImageSavedCallback {
           override fun onError(exc: ImageCaptureException) {
               Log.e(TAG, "Photo capture failed: ${exc.message}", exc)
           }

           override fun
               onImageSaved(output: ImageCapture.OutputFileResults){
               val msg = "Photo capture succeeded: ${output.savedUri}"
               Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
               Log.d(TAG, msg)
           }
       }
   )
}

首先,获取对 ImageCapture 用例的引用。如果用例为 null,退出函数。如果在设置图片拍摄之前点按“photo”按钮,它将为 null。如果没有 return 语句,应用会在该用例为 null 时崩溃。

val imageCapture = imageCapture ?: return

接下来,创建用于保存图片的 MediaStore 内容值。使用时间戳,确保 MediaStore 中的显示名是唯一的。

val name = SimpleDateFormat(FILENAME_FORMAT, Locale.US)
              .format(System.currentTimeMillis())
   val contentValues = ContentValues().apply {
       put(MediaStore.MediaColumns.DISPLAY_NAME, name)
       put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
       if(Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
           put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/CameraX-Image")
       }
   }

创建一个 OutputFileOptions 对象。在该对象中,可以指定所需的输出内容。我们希望将输出保存在 MediaStore 中,以便其他应用可以显示它,因此,添加 MediaStore 条目。

val outputOptions = ImageCapture.OutputFileOptions
       .Builder(contentResolver,
                MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                contentValues)
       .build()

对 imageCapture 对象调用 takePicture()。传入 outputOptions、执行器和保存图片时使用的回调。接下来,需要完成回调。

imageCapture.takePicture(
   outputOptions, ContextCompat.getMainExecutor(this),
   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 = Uri.fromFile(photoFile)
   val msg = "Photo capture succeeded: $savedUri"
   Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
   Log.d(TAG, msg)
}

找到 startCamera() 方法,并将此代码复制到要预览的代码下方。

imageCapture = ImageCapture.Builder().build()

最后,在 try 代码块中更新对 bindToLifecycle() 的调用,以包含新的用例:

cameraProvider.bindToLifecycle(
   this, cameraSelector, preview, imageCapture)

此时,该方法将如下所示:

private fun startCamera() {
   val cameraProviderFuture = ProcessCameraProvider.getInstance(this)

   cameraProviderFuture.addListener({
       // Used to bind the lifecycle of cameras to the lifecycle owner
       val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()

       // Preview
       val preview = Preview.Builder()
           .build()
           .also {
                 it.setSurfaceProvider(viewFinder.surfaceProvider)
           }

       imageCapture = ImageCapture.Builder()
           .build()

       // Select back camera as a default
       val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA

       try {
           // Unbind use cases before rebinding
           cameraProvider.unbindAll()

           // Bind use cases to camera
           cameraProvider.bindToLifecycle(
               this, cameraSelector, preview, imageCapture)

       } catch(exc: Exception) {
           Log.e(TAG, "Use case binding failed", exc)
       }

   }, ContextCompat.getMainExecutor(this))
}

重新运行应用,然后按 Take Photo。屏幕上应该会显示一个消息框,会在日志中看到一条消息。
Android CameraX的基础使用
这时可以查看本地的图片库,查看刚刚拍摄的图片。
Android CameraX的基础使用

实现 ImageAnalysis 用例

需要Android 10 以及更高版本的设备,建议使用实体设备来测试这部分代码

使用 ImageAnalysis 功能可让相机应用变得更加有趣。它允许定义实现 ImageAnalysis.Analyzer 接口的自定义类,并使用传入的相机帧调用该类。无需管理相机会话状态,甚至无需处理图像;与其他生命周期感知型组件一样,仅绑定到应用所需的生命周期就足够了。

将此分析器添加为 MainActivity.kt 中的内部类。分析器会记录图像的平均亮度。如需创建分析器,我们会替换实现 ImageAnalysis.Analyzer 接口的类中的 analyze 函数。

private class LuminosityAnalyzer(private val listener: LumaListener) : ImageAnalysis.Analyzer {

   private fun ByteBuffer.toByteArray(): ByteArray {
       rewind()    // Rewind the buffer to zero
       val data = ByteArray(remaining())
       get(data)   // Copy the buffer into a byte array
       return data // Return the byte array
   }

   override fun analyze(image: ImageProxy) {

       val buffer = image.planes[0].buffer
       val data = buffer.toByteArray()
       val pixels = data.map { it.toInt() and 0xFF }
       val luma = pixels.average()

       listener(luma)

       image.close()
   }
}

在类中实现 ImageAnalysis.Analyzer 接口后,只需在 ImageAnalysis中实例化一个 LuminosityAnalyzer 实例(与其他用例类似),并再次更新 startCamera() 函数,然后调用 CameraX.bindToLifecycle() 即可:

接下来更新startCamera(),将以下代码添加到 imageCapture 代码下方。

val imageAnalyzer = ImageAnalysis.Builder()
   .build()
   .also {
       it.setAnalyzer(cameraExecutor, LuminosityAnalyzer { luma ->
           Log.d(TAG, "Average luminosity: $luma")
       })
   }

更新 cameraProvider 上的 bindToLifecycle() 调用,以包含 imageAnalyzer。

cameraProvider.bindToLifecycle(
   this, cameraSelector, preview, imageCapture, imageAnalyzer)

现在,完整的方法将如下所示:

private fun startCamera() {
   val cameraProviderFuture = ProcessCameraProvider.getInstance(this)

   cameraProviderFuture.addListener({
       // Used to bind the lifecycle of cameras to the lifecycle owner
       val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()

       // Preview
       val preview = Preview.Builder()
           .build()
           .also {
               it.setSurfaceProvider(viewBinding.viewFinder.surfaceProvider)
           }

       imageCapture = ImageCapture.Builder()
           .build()

       val imageAnalyzer = ImageAnalysis.Builder()
           .build()
           .also {
               it.setAnalyzer(cameraExecutor, LuminosityAnalyzer { luma ->
                   Log.d(TAG, "Average luminosity: $luma")
               })
           }

       // Select back camera as a default
       val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA

       try {
           // Unbind use cases before rebinding
           cameraProvider.unbindAll()

           // Bind use cases to camera
           cameraProvider.bindToLifecycle(
               this, cameraSelector, preview, imageCapture, imageAnalyzer)

       } catch(exc: Exception) {
           Log.e(TAG, "Use case binding failed", exc)
       }

   }, ContextCompat.getMainExecutor(this))
}

立即运行应用!它会大约每秒在 logcat 中生成一个类似于下面的消息。

D/CameraXApp: Average luminosity: ...

实现 VideoCapture 用例(拍摄视频)

VideoCapture 用例是 CameraX 用例系列的新成员,建议使用实体设备进行测试

CameraX 在 1.1.0-alpha10 版中添加了 VideoCapture 用例,并且从那以后一直在改进。注意,VideoCapture API 支持很多视频捕获功能,因此,为了使此项目易于管理,仅演示如何在 MediaStore 中捕获视频和音频。

  1. 将以下代码复制到captureVideo() 方法:该方法可以控制 VideoCapture 用例的启动和停止。
// Implements VideoCapture use case, including start and stop capturing.
private fun captureVideo() {
   val videoCapture = this.videoCapture ?: return

   viewBinding.videoCaptureButton.isEnabled = false

   val curRecording = recording
   if (curRecording != null) {
       // Stop the current recording session.
       curRecording.stop()
       recording = null
       return
   }

   // create and start a new recording session
   val name = SimpleDateFormat(FILENAME_FORMAT, Locale.US)
              .format(System.currentTimeMillis())
   val contentValues = ContentValues().apply {
       put(MediaStore.MediaColumns.DISPLAY_NAME, name)
       put(MediaStore.MediaColumns.MIME_TYPE, "video/mp4")
       if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
           put(MediaStore.Video.Media.RELATIVE_PATH, "Movies/CameraX-Video")
       }
   }

   val mediaStoreOutputOptions = MediaStoreOutputOptions
       .Builder(contentResolver, MediaStore.Video.Media.EXTERNAL_CONTENT_URI)
       .setContentValues(contentValues)
       .build()
   recording = videoCapture.output
       .prepareRecording(this, mediaStoreOutputOptions)
       .apply {
           if (PermissionChecker.checkSelfPermission(this@MainActivity,
                   Manifest.permission.RECORD_AUDIO) ==
               PermissionChecker.PERMISSION_GRANTED)
           {
               withAudioEnabled()
           }
       }
       .start(ContextCompat.getMainExecutor(this)) { recordEvent ->
           when(recordEvent) {
               is VideoRecordEvent.Start -> {
                   viewBinding.videoCaptureButton.apply {
                       text = getString(R.string.stop_capture)
                       isEnabled = true
                   }
               }
               is VideoRecordEvent.Finalize -> {
                   if (!recordEvent.hasError()) {
                       val msg = "Video capture succeeded: " +
                           "${recordEvent.outputResults.outputUri}"
                       Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT)
                            .show()
                       Log.d(TAG, msg)
                   } else {
                       recording?.close()
                       recording = null
                       Log.e(TAG, "Video capture ends with error: " +
                           "${recordEvent.error}")
                   }
                   viewBinding.videoCaptureButton.apply {
                       text = getString(R.string.start_capture)
                       isEnabled = true
                   }
               }
           }
       }
}

以下分析这段代码。首先检查是否已创建 VideoCapture 用例:如果尚未创建,则不执行任何操作。

val videoCapture = videoCapture ?: return

在 CameraX 完成请求操作之前,停用界面;在后续步骤中,它会在已注册的 VideoRecordListener 内重新启用。

viewBinding.videoCaptureButton.isEnabled = false

如果有正在进行的录制操作,将其停止并释放当前的 recording。当所捕获的视频文件可供应用使用时,我们会收到通知。

val curRecording = recording
if (curRecording != null) {
    curRecording.stop()
    recording = null
    return
}

为了开始录制,创建一个新的录制会话。首先,创建预定的 MediaStore 视频内容对象,将系统时间戳作为显示名(以便可以捕获多个视频)。

val name = SimpleDateFormat(FILENAME_FORMAT, Locale.US)
           .format(System.currentTimeMillis())
val contentValues = ContentValues().apply {
       put(MediaStore.MediaColumns.DISPLAY_NAME, name)
       put(MediaStore.MediaColumns.MIME_TYPE, "video/mp4")
       if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
           put(MediaStore.Video.Media.RELATIVE_PATH,
               "Movies/CameraX-Video")
       }
}

使用外部内容选项创建 MediaStoreOutputOptions.Builder。

val mediaStoreOutputOptions = MediaStoreOutputOptions
      .Builder(contentResolver,
               MediaStore.Video.Media.EXTERNAL_CONTENT_URI)

将创建的视频 contentValues 设置为 MediaStoreOutputOptions.Builder,并构建我们的 MediaStoreOutputOptions 实例。

.setContentValues(contentValues)
.build()

将输出选项配置为 VideoCapture 的 Recorder 并启用录音:

  videoCapture
  .output
  .prepareRecording(this, mediaStoreOutputOptions)
.withAudioEnabled()

在此录音中启用音频。

.apply {
   if (PermissionChecker.checkSelfPermission(this@MainActivity,
           Manifest.permission.RECORD_AUDIO) ==
       PermissionChecker.PERMISSION_GRANTED)
   {
       withAudioEnabled()
   }
}

启动这项新录制内容,并注册一个 lambda VideoRecordEvent 监听器。

.start(ContextCompat.getMainExecutor(this)) { recordEvent ->
   //lambda event listener
}

当相机设备开始请求录制时,将“Start Capture”按钮文本切换为“Stop Capture”。

is VideoRecordEvent.Start -> {
    viewBinding.videoCaptureButton.apply {
        text = getString(R.string.stop_capture)
        isEnabled = true
    }
}

完成录制后,用消息框通知用户,并将“Stop Capture”按钮切换回“Start Capture”,然后重新启用它:

is VideoRecordEvent.Finalize -> {
   if (!recordEvent.hasError()) {
       val msg = "Video capture succeeded: " +
                 "${recordEvent.outputResults.outputUri}"
       Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT)
            .show()
       Log.d(TAG, msg)
   } else {
       recording?.close()
       recording = null
       Log.e(TAG, "Video capture succeeded: " +
                  "${recordEvent.outputResults.outputUri}")
   }
   viewBinding.videoCaptureButton.apply {
       text = getString(R.string.start_capture)
       isEnabled = true
   }
}
  1. 在 startCamera() 中,将以下代码放置在 preview 创建行之后。这将创建 VideoCapture 用例。
val recorder = Recorder.Builder()
   .setQualitySelector(QualitySelector.from(Quality.HIGHEST))
   .build()
videoCapture = VideoCapture.withOutput(recorder)

(可选)同样在 startCamera() 中,通过删除或注释掉以下代码来停用 imageCapture 和 imageAnalyzer 用例:

/* comment out ImageCapture and ImageAnalyzer use cases
imageCapture = ImageCapture.Builder().build()

val imageAnalyzer = ImageAnalysis.Builder()
   .build()
   .also {
       it.setAnalyzer(cameraExecutor, LuminosityAnalyzer { luma ->
           Log.d(TAG, "Average luminosity: $luma")
       })
   }
*/
  1. 将 Preview + VideoCapture 用例绑定到生命周期相机。仍在 startCamera() 内,将 cameraProvider.bindToLifecycle() 调用替换为以下代码:
// Bind use cases to camera
cameraProvider.bindToLifecycle(this, cameraSelector, preview, videoCapture)
  1. 构建并运行项目。
  2. 录制一些剪辑:
  • 按“START CAPTURE”按钮。注意,图片说明会变为“STOP CAPTURE”。
  • 录制几秒钟或几分钟的视频。
  • 按“STOP CAPTURE”按钮(和 start capture 按钮是同一个按钮)。

视频录制测试可以看到视频被成功保存至媒体库。文章来源地址https://www.toymoban.com/news/detail-493592.html

到了这里,关于Android CameraX的基础使用的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 使用 CameraX 在 Jetpack Compose 中构建相机 Android 应用程序

    CameraX 是一个 Jetpack 库,旨在帮助简化相机应用程序的开发。 [camerax官方文档] https://developer.android.com/training/camerax CameraX的几个用例: Image Capture Video Capture Preview Image analyze 具体如何使用相关用例,请查看上面的官方链接。 下面仅就视频录制用例来叙述相关实现流程。 添加

    2024年02月06日
    浏览(56)
  • android camera系列(Camera1、Camera2、CameraX)的使用以及输出的图像格式

    1.1.1、布局 1.1.2、实现预览 Camera.open() 打开摄像头 setPreviewDisplay 设置预览展示的控件 startPreview 开始预览 发现预览是横着的,需要使用 setDisplayOrientation 调整预览图像的方向 1.1.3、获取摄像头的原始数据 setPreviewCallback 设置预览数据的回调 2560*1440 默认返回图像的分辨率 Image

    2024年02月21日
    浏览(37)
  • valgrind基本功能介绍、基础使用方法说明 valgrind基本功能介绍、基础使用方法说明

    valgrind基本功能介绍、基础使用方法说明_valgrind使用方法_HNU Latecomer的博客-CSDN博客 拷贝效果不好,请看原文。 1、Valgrind概述 Valgrind是一套Linux下,开放源代码(GPL V2)的仿真调试工具的集合。 Valgrind由内核(core)以及基于内核的其他调试工具组成。内核类似于一个框架(f

    2024年02月07日
    浏览(50)
  • Android service介绍——startService和bindService基本使用(1)

    在安卓日常开发和学习中,经常会使用到Service,它是Android四大组件之一。使用Service可以在后台执行长时间的操作( perform long-running operations in the background ),Service并不与用户产生UI交互。其他的应用组件可以启动Service,即便用户切换了其他应用,启动的Service仍可在后台运行。

    2024年02月05日
    浏览(25)
  • 【postman 基础篇一】基本介绍与使用

    Postman 是一款支持 HTTP 协议的接口调试与测试工具,其主要特点就是功能强大,使用简单且易用性好。无论是开发人员进行接口调试,还是测试人员做接口测试,Postman 都是首选工具之一。接下来就介绍一下 Postman 到底有哪些功能,它们分别都能干些什么。下面先通过一张图来

    2024年02月12日
    浏览(24)
  • Android 实现相机(CameraX)预览

    CameraX 是一个 Jetpack 库,旨在帮助您更轻松地开发相机应用。 对于新应用,我们建议从 CameraX 开始。它提供一致且易于使用的 API,适用于绝大多数 Android 设备,并向后兼容 Android 5.0(API 级别 21)。 CameraX 支持大多数常见的相机用例: 预览 :在屏幕上查看图片。 图片分析 :

    2024年02月15日
    浏览(28)
  • Android CameraX适配Android13的踩坑之路

    最近把AGP插件升级到8.1.0,新建项目的时候目标版本和编译版本都是33,发现之前的demo使用Camerax拍照和录像都失败了,于是查看了一下官网和各种资料,找到了Android13的适配方案. 与早期版本一样,Android 13 包含一些行为变更,这些变更可能会影响您的应用。以下行为变更仅影

    2024年02月12日
    浏览(63)
  • Android开发之Compose基础学习-Surface容器的基本使用

    先上图 再看代码

    2024年02月15日
    浏览(34)
  • 【Android】一步步实现手机拍照、录像及存储至相册(CameraX)

    参考资料 https://developer.android.google.cn/codelabs/camerax-getting-started?hl=zh-cn#0 https://developer.android.google.cn/training/camerax/video-capture?hl=zh-cn //https://blog.csdn.net/teolih/article/details/120423971 对应视频及项目代码 【项目代码】https://gitee.com/hellosunshine/camerax_android_java.git 步骤(java版本) 基本功能

    2024年02月07日
    浏览(33)
  • Android相机调用-CameraX【外接摄像头】【USB摄像头】

    Android相机调用有原生的Camera和Camera2,我觉得调用代码都太复杂了,CameraX调用代码简洁很多。 说明文档:https://developer.android.com/jetpack/androidx/releases/camera?hl=zh-cn 现有查到的调用资料都不够新,对于外接摄像头(USB摄像头)这类非前置也非后置摄像头的设备调用,都说是没有实

    2024年02月09日
    浏览(36)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包