当前位置:   article > 正文

安卓开发之camerax介绍(1)——取景器、拍摄照片、录制视频入门实战_android camerax

android camerax

问题背景

本文将初步介绍jetpeck库之camerax的初步基本使用,包括实现取景器、拍摄照片、录制视频三个功能用例。

问题分析

话不多说,直接上代码。

(1)项目中添加camerax相关依赖;

  1. def camerax_version = "1.1.0-beta01"
  2. implementation "androidx.camera:camera-core:${camerax_version}"
  3. implementation "androidx.camera:camera-camera2:${camerax_version}"
  4. implementation "androidx.camera:camera-lifecycle:${camerax_version}"
  5. implementation "androidx.camera:camera-video:${camerax_version}"
  6. implementation "androidx.camera:camera-view:${camerax_version}"
  7. implementation "androidx.camera:camera-extensions:${camerax_version}"

(2)项目中使用到了viewbinding,需要相应配置;

  1. android {
  2. compileSdkVersion 31
  3. defaultConfig {
  4. applicationId "composer.kotlin"
  5. ...
  6. }
  7. ...
  8. buildFeatures{
  9. // 配置开启viewbinding
  10. viewBinding = true
  11. }
  12. }

(3)新建activity,代码如下:

  1. import android.Manifest
  2. import android.content.ContentValues
  3. import android.content.pm.PackageManager
  4. import android.os.Build
  5. import android.os.Bundle
  6. import android.provider.MediaStore
  7. import android.util.Log
  8. import android.widget.Toast
  9. import androidx.appcompat.app.AppCompatActivity
  10. import androidx.camera.core.*
  11. import androidx.camera.lifecycle.ProcessCameraProvider
  12. import androidx.camera.video.*
  13. import androidx.camera.video.VideoCapture
  14. import androidx.core.app.ActivityCompat
  15. import androidx.core.content.ContextCompat
  16. import androidx.core.content.PermissionChecker
  17. import composer.R
  18. import composer.databinding.ActivityCameraxBinding
  19. import kotlinx.android.synthetic.main.activity_camerax.*
  20. import java.nio.ByteBuffer
  21. import java.text.SimpleDateFormat
  22. import java.util.*
  23. import java.util.concurrent.ExecutorService
  24. import java.util.concurrent.Executors
  25. typealias LumaListener = (luma: Double) -> Unit
  26. class CameraxActivity : AppCompatActivity() {
  27. private lateinit var viewBinding: ActivityCameraxBinding
  28. private var imageCapture: ImageCapture? = null
  29. private var videoCapture: VideoCapture<Recorder>? = null
  30. private var recording: Recording? = null
  31. private lateinit var cameraExecutor: ExecutorService
  32. override fun onCreate(savedInstanceState: Bundle?) {
  33. super.onCreate(savedInstanceState)
  34. viewBinding = ActivityCameraxBinding.inflate(layoutInflater)
  35. setContentView(viewBinding.root)
  36. if (allPermissionsGranted()) {
  37. startCamera()
  38. } else {
  39. ActivityCompat.requestPermissions(
  40. this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS)
  41. }
  42. viewBinding.imageCaptureButton.setOnClickListener { takePhoto() }
  43. viewBinding.videoCaptureButton.setOnClickListener { captureVideo() }
  44. cameraExecutor = Executors.newSingleThreadExecutor()
  45. }
  46. /**
  47. * 捕获图片
  48. */
  49. private fun takePhoto() {
  50. val imageCapture = imageCapture ?: return
  51. val name = SimpleDateFormat(FILENAME_FORMAT, Locale.US)
  52. .format(System.currentTimeMillis())
  53. val contentValues = ContentValues().apply {
  54. put(MediaStore.MediaColumns.DISPLAY_NAME, name)
  55. put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
  56. if(Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
  57. put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/CameraX-Image")
  58. }
  59. }
  60. val outputOptions = ImageCapture.OutputFileOptions
  61. .Builder(contentResolver,
  62. MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
  63. contentValues)
  64. .build()
  65. imageCapture.takePicture(
  66. outputOptions,
  67. ContextCompat.getMainExecutor(this),
  68. object : ImageCapture.OnImageSavedCallback {
  69. override fun onError(exc: ImageCaptureException) {
  70. Log.e(TAG, "Photo capture failed: ${exc.message}", exc)
  71. }
  72. override fun
  73. onImageSaved(output: ImageCapture.OutputFileResults){
  74. val msg = "Photo capture succeeded: ${output.savedUri}"
  75. Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
  76. Log.d(TAG, msg)
  77. }
  78. }
  79. )
  80. }
  81. /**
  82. * 捕获视频
  83. */
  84. private fun captureVideo() {
  85. val videoCapture = this.videoCapture ?: return
  86. viewBinding.videoCaptureButton.isEnabled = false
  87. val curRecording = recording
  88. if (curRecording != null) {
  89. // 停止当前录制
  90. curRecording.stop()
  91. recording = null
  92. return
  93. }
  94. val name = SimpleDateFormat(FILENAME_FORMAT, Locale.US)
  95. .format(System.currentTimeMillis())
  96. val contentValues = ContentValues().apply {
  97. put(MediaStore.MediaColumns.DISPLAY_NAME, name)
  98. put(MediaStore.MediaColumns.MIME_TYPE, "video/mp4")
  99. if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
  100. put(MediaStore.Video.Media.RELATIVE_PATH, "Movies/CameraX-Video")
  101. }
  102. }
  103. val mediaStoreOutputOptions = MediaStoreOutputOptions
  104. .Builder(contentResolver, MediaStore.Video.Media.EXTERNAL_CONTENT_URI)
  105. .setContentValues(contentValues)
  106. .build()
  107. recording = videoCapture.output
  108. .prepareRecording(this, mediaStoreOutputOptions)
  109. .apply {
  110. if (PermissionChecker.checkSelfPermission(this@CameraxActivity,
  111. Manifest.permission.RECORD_AUDIO) ==
  112. PermissionChecker.PERMISSION_GRANTED)
  113. {
  114. withAudioEnabled()
  115. }
  116. }
  117. .start(ContextCompat.getMainExecutor(this)) { recordEvent ->
  118. when(recordEvent) {
  119. is VideoRecordEvent.Start -> {
  120. viewBinding.videoCaptureButton.apply {
  121. text = getString(R.string.stop_capture)
  122. isEnabled = true
  123. }
  124. }
  125. is VideoRecordEvent.Finalize -> {
  126. if (!recordEvent.hasError()) {
  127. val msg = "Video capture succeeded: " +
  128. "${recordEvent.outputResults.outputUri}"
  129. Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT)
  130. .show()
  131. Log.d(TAG, msg)
  132. } else {
  133. recording?.close()
  134. recording = null
  135. Log.e(TAG, "Video capture ends with error: " +
  136. "${recordEvent.error}")
  137. }
  138. viewBinding.videoCaptureButton.apply {
  139. text = getString(R.string.start_capture)
  140. isEnabled = true
  141. }
  142. }
  143. }
  144. }
  145. }
  146. /**
  147. * 启动相机,开机相机取景预览
  148. */
  149. private fun startCamera() {
  150. val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
  151. cameraProviderFuture.addListener({
  152. val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
  153. // 开启相机取景预览
  154. val preview = Preview.Builder()
  155. .build()
  156. .also {
  157. it.setSurfaceProvider(viewFinder.surfaceProvider)
  158. }
  159. imageCapture = ImageCapture.Builder()
  160. .build()
  161. val recorder = Recorder.Builder()
  162. .setQualitySelector(QualitySelector.from(Quality.HIGHEST))
  163. .build()
  164. videoCapture = VideoCapture.withOutput(recorder)
  165. val imageAnalyzer = ImageAnalysis.Builder()
  166. .build()
  167. .also {
  168. it.setAnalyzer(cameraExecutor, LuminosityAnalyzer { luma ->
  169. Log.d(TAG, "Average luminosity: $luma")
  170. })
  171. }
  172. // 默认使用后置摄像头
  173. val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
  174. try {
  175. cameraProvider.unbindAll()
  176. // 混合使用preview、imageCapture、和videoCapture三个用例
  177. cameraProvider.bindToLifecycle(
  178. this, cameraSelector, preview, imageCapture, videoCapture)
  179. // 注意:不支持 preview + imageCapture + videoCapture + imageAnalysis 组合
  180. // cameraProvider.bindToLifecycle(
  181. // this, cameraSelector, preview, imageAnalyzer, imageCapture, videoCapture)
  182. } catch(exc: Exception) {
  183. Log.e(TAG, "Use case binding failed", exc)
  184. }
  185. }, ContextCompat.getMainExecutor(this))
  186. }
  187. private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
  188. ContextCompat.checkSelfPermission(
  189. baseContext, it) == PackageManager.PERMISSION_GRANTED
  190. }
  191. override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
  192. super.onRequestPermissionsResult(requestCode, permissions, grantResults)
  193. if (requestCode == REQUEST_CODE_PERMISSIONS) {
  194. if (allPermissionsGranted()) {
  195. startCamera()
  196. } else {
  197. Toast.makeText(this,
  198. "Permissions not granted by the user.",
  199. Toast.LENGTH_SHORT).show()
  200. finish()
  201. }
  202. }
  203. }
  204. override fun onDestroy() {
  205. super.onDestroy()
  206. cameraExecutor.shutdown()
  207. }
  208. companion object {
  209. private const val TAG = "baorant"
  210. private const val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS"
  211. private const val REQUEST_CODE_PERMISSIONS = 10
  212. private val REQUIRED_PERMISSIONS =
  213. mutableListOf (
  214. Manifest.permission.CAMERA,
  215. Manifest.permission.RECORD_AUDIO
  216. ).apply {
  217. if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
  218. add(Manifest.permission.WRITE_EXTERNAL_STORAGE)
  219. }
  220. }.toTypedArray()
  221. }
  222. private class LuminosityAnalyzer(private val listener: LumaListener) : ImageAnalysis.Analyzer {
  223. private fun ByteBuffer.toByteArray(): ByteArray {
  224. rewind()
  225. val data = ByteArray(remaining())
  226. get(data)
  227. return data
  228. }
  229. override fun analyze(image: ImageProxy) {
  230. val buffer = image.planes[0].buffer
  231. val data = buffer.toByteArray()
  232. val pixels = data.map { it.toInt() and 0xFF }
  233. val luma = pixels.average()
  234. listener(luma)
  235. image.close()
  236. }
  237. }
  238. }

(4)activity对应layout布局文件如下

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <androidx.constraintlayout.widget.ConstraintLayout
  3. xmlns:android="http://schemas.android.com/apk/res/android"
  4. xmlns:app="http://schemas.android.com/apk/res-auto"
  5. xmlns:tools="http://schemas.android.com/tools"
  6. android:layout_width="match_parent"
  7. android:layout_height="match_parent"
  8. tools:context=".view.CameraxActivity">
  9. <androidx.camera.view.PreviewView
  10. android:id="@+id/viewFinder"
  11. android:layout_width="match_parent"
  12. android:layout_height="match_parent" />
  13. <Button
  14. android:id="@+id/image_capture_button"
  15. android:layout_width="110dp"
  16. android:layout_height="110dp"
  17. android:layout_marginBottom="50dp"
  18. android:layout_marginEnd="50dp"
  19. android:elevation="2dp"
  20. android:text="@string/take_photo"
  21. app:layout_constraintBottom_toBottomOf="parent"
  22. app:layout_constraintLeft_toLeftOf="parent"
  23. app:layout_constraintEnd_toStartOf="@id/vertical_centerline" />
  24. <Button
  25. android:id="@+id/video_capture_button"
  26. android:layout_width="110dp"
  27. android:layout_height="110dp"
  28. android:layout_marginBottom="50dp"
  29. android:layout_marginStart="50dp"
  30. android:elevation="2dp"
  31. android:text="@string/start_capture"
  32. app:layout_constraintBottom_toBottomOf="parent"
  33. app:layout_constraintStart_toEndOf="@id/vertical_centerline" />
  34. <androidx.constraintlayout.widget.Guideline
  35. android:id="@+id/vertical_centerline"
  36. android:layout_width="wrap_content"
  37. android:layout_height="wrap_content"
  38. android:orientation="vertical"
  39. app:layout_constraintGuide_percent=".50" />
  40. </androidx.constraintlayout.widget.ConstraintLayout>

(5)使用到的字符串资源如下:

  1. <string name="take_photo">拍照</string>
  2. <string name="start_capture">开始捕捉</string>
  3. <string name="stop_capture">停止捕获</string>

(6)运行结果如下:

image.png 实现取景器功能,点击拍照功能可以捕捉图像存入手机本地。 点击开始捕获按钮开始视频捕获,停止后视频保存到本地。

问题总结

本文初步介绍了jetpeck库之camerax的初步基本使用,包括实现取景器、拍摄照片、录制视频三个功能用例,有兴趣的同学可以进一步深入研究。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/461499
推荐阅读
相关标签
  

闽ICP备14008679号