【问题标题】:CameraX Video capture not recording on physical deviceCameraX 视频捕获未在物理设备上录制
【发布时间】:2022-04-12 02:41:53
【问题描述】:

你好。我对 CameraX VideoCapture 用例有疑问。我正在使用以下版本的库:

implementation "androidx.camera:camera-video:1.1.0-alpha11"

我使用的用例如下:

  • MediaModule.kt(刀柄)

      @Module
      @InstallIn(SingletonComponent::class)
      class VideoCaptureModule {
      @Provides
      fun provideMediaExecutor(): ExecutorService = Executors.newSingleThreadExecutor()
    
      @Provides
      fun provideCameraProviderFuture(
          @ApplicationContext ctx: Context
      ): ListenableFuture<ProcessCameraProvider> = ProcessCameraProvider.getInstance(ctx)
    
      @Provides
      fun provideCameraPreview(): Preview = Preview.Builder().build()
    
      @Provides
      fun provideCameraQualitySelector(): QualitySelector = QualitySelector
          .firstTry(QualitySelector.QUALITY_UHD)
          .thenTry(QualitySelector.QUALITY_FHD)
          .thenTry(QualitySelector.QUALITY_HD)
          .finallyTry(QualitySelector.QUALITY_SD, QualitySelector.FALLBACK_STRATEGY_LOWER)
    
      @Provides
      fun provideCameraRecorder(
          cameraExecutor: ExecutorService,
          qualitySelector: QualitySelector
      ): Recorder = Recorder.Builder()
          .setExecutor(cameraExecutor)
          .setQualitySelector(qualitySelector)
          .build()
    
      @Provides
      fun provideVideoCapture(recorder: Recorder): VideoCapture<Recorder> =
          VideoCapture.withOutput(recorder)
      }
    
  • 记录视图模型(AndroidViewModel)

      @HiltViewModel
      class RecordVideoViewModel @Inject constructor(
          private val preview: Preview,
          private val app: Application,
          private val cameraExecutor: ExecutorService,
          private val videoCapture: VideoCapture<Recorder>,
          private val cameraProviderFuture: ListenableFuture<ProcessCameraProvider>
      ) : AndroidViewModel(app) {
    
          private var camera: Camera? = null
          private var activeRecording: ActiveRecording? = null
          private var cameraProvider: ProcessCameraProvider? = null
    
          private val _videoRecordingStatus = MutableLiveData<VideoRecordingStatus>()
          val videoRecordingStatus = _videoRecordingStatus as LiveData<VideoRecordingStatus>
    
          fun startCamera(
              waitForReload: Boolean,
              lifecycleOwner: LifecycleOwner,
              cameraSelector: CameraSelector,
              surfaceProvider: Preview.SurfaceProvider
          ) {
              cameraProviderFuture.addListener({
                  cameraProvider = cameraProviderFuture.get()
                  preview.setSurfaceProvider(surfaceProvider)
    
                  try {
                      viewModelScope.launch {
                          cameraProvider?.unbindAll()
                          if (waitForReload) delay(CAMERA_WAITING_TIME)
                          camera = cameraProvider
                              ?.bindToLifecycle(lifecycleOwner, cameraSelector, preview, videoCapture)
                          _torchAvailable.value = camera?.cameraInfo?.hasFlashUnit() == true
                      }
                  } catch (t: Throwable) {
                      FirebaseCrashlytics.getInstance().log(t.localizedMessage.orEmpty())
                  }
              }, ContextCompat.getMainExecutor(app))
          }
    
          fun stopCamera() {
              cameraProvider?.unbindAll()
              cameraProvider = null
              camera = null
              torchEnabled = false
    
              try {
                  activeRecording?.stop()
              } catch (t: IllegalStateException) {
                  // Ignore, active recording already stopped.
              }
              activeRecording = null
          }    
    
          fun startRecording() {
              val name = "${app.getString(R.string.app_name)}-${Date().time}"
              val contentValues = ContentValues().apply {
                  put(MediaStore.Video.Media.DISPLAY_NAME, name)
              }
              val mediaStoreOutput = MediaStoreOutputOptions.Builder(
                  app.contentResolver,
                  MediaStore.Video.Media.EXTERNAL_CONTENT_URI
              ).setContentValues(contentValues).build()
    
              activeRecording = videoCapture.output.prepareRecording(app, mediaStoreOutput)
                  .withAudioEnabled()
                  .withEventListener(cameraExecutor, { event ->
                      when (event) {
                          is VideoRecordEvent.Start ->
                              _videoRecordingStatus.postValue(VideoRecordingStatus.Recording)
                          is VideoRecordEvent.Finalize -> _videoRecordingStatus.postValue(
                              event.cause?.run(VideoRecordingStatus::Failed)
                                  ?: VideoRecordingStatus.Succeeded(event.outputResults.outputUri.toString())
                          )
                      }
                  }).start()
          }
    
          fun stopRecording() {
              activeRecording?.stop()
          }
    
          override fun onCleared() {
              super.onCleared()
              stopCamera()
              cameraExecutor.shutdown()
          }
    
          sealed class VideoRecordingStatus {
              object Recording : VideoRecordingStatus()
              data class Failed(val t: Throwable?) : VideoRecordingStatus()
              data class Succeeded(val uri: String) : VideoRecordingStatus()
          }
      }
    

在模拟器中一切正常。我可以录制和保存视频没有问题,但是,当我在运行 Android 11 的真实设备(Samsung Galaxy S20 Ultra 5G)上运行该应用程序时,我会遇到以下崩溃:

2021-12-15 11:56:00.271 3758-3789/? E/DatabaseUtils: Writing exception to parcel
    java.lang.IllegalArgumentException: MIME type application/octet-stream cannot be inserted into content://media/external/video/media; expected MIME type under video/*
        at com.android.providers.media.MediaProvider.ensureFileColumns(MediaProvider.java:3301)
        at com.android.providers.media.MediaProvider.ensureUniqueFileColumns(MediaProvider.java:3072)
        at com.android.providers.media.MediaProvider.insertFile(MediaProvider.java:3826)
        at com.android.providers.media.MediaProvider.insertInternal(MediaProvider.java:4386)
        at com.android.providers.media.MediaProvider.insert(MediaProvider.java:4110)
        at android.content.ContentProvider$Transport.insert(ContentProvider.java:336)
        at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:167)
        at android.os.Binder.execTransactInternal(Binder.java:1190)
        at android.os.Binder.execTransact(Binder.java:1159)
2021-12-15 11:56:00.275 19974-4965/? E/SequentialExecutor: Exception while executing runnable androidx.camera.core.impl.utils.executor.SequentialExecutor$1@9d3f8d3
    java.lang.IllegalArgumentException: MIME type application/octet-stream cannot be inserted into content://media/external/video/media; expected MIME type under video/*
        at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:172)
        at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:142)
        at android.content.ContentProviderProxy.insert(ContentProviderNative.java:549)
        at android.content.ContentResolver.insert(ContentResolver.java:2159)
        at android.content.ContentResolver.insert(ContentResolver.java:2121)
        at androidx.camera.video.Recorder$RecordingRecord.lambda$initializeRecording$1(Recorder.java:2247)
        at androidx.camera.video.Recorder$RecordingRecord$$ExternalSyntheticLambda1.get(Unknown Source:4)
        at androidx.camera.video.Recorder$RecordingRecord.performOneTimeMediaMuxerCreation(Recorder.java:2394)
        at androidx.camera.video.Recorder.setupAndStartMediaMuxer(Recorder.java:1298)
        at androidx.camera.video.Recorder$2.onEncodedData(Recorder.java:1436)
        at androidx.camera.video.internal.encoder.EncoderImpl$MediaCodecCallback.lambda$sendEncodedData$3(EncoderImpl.java:937)
        at androidx.camera.video.internal.encoder.EncoderImpl$MediaCodecCallback$$ExternalSyntheticLambda6.run(Unknown Source:4)
        at androidx.camera.core.impl.utils.executor.SequentialExecutor$1.run(SequentialExecutor.java:111)
        at androidx.camera.core.impl.utils.executor.SequentialExecutor$QueueWorker.workOnQueue(SequentialExecutor.java:231)
        at androidx.camera.core.impl.utils.executor.SequentialExecutor$QueueWorker.run(SequentialExecutor.java:173)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
        at java.lang.Thread.run(Thread.java:923)
2021-12-15 11:56:00.328 19974-4965/? E/AndroidRuntime: FATAL EXCEPTION: pool-40-thread-1
    Process: com.pt.wshhp, PID: 19974
    java.lang.AssertionError: One-time media muxer creation has already occurred for recording RecordingRecord{getOutputOptions=MediaStoreOutputOptions{contentResolver=android.app.ContextImpl$ApplicationContentResolver@e6e1f6b, collectionUri=content://media/external/video/media, contentValues=_display_name=Worldstar-1639590959896, fileSizeLimit=0}, getCallbackExecutor=java.util.concurrent.Executors$FinalizableDelegatedExecutorService@850110f, getEventListener=com.pt.wshhp.viewmodels.RecordVideoViewModel$$ExternalSyntheticLambda0@fe4059c, hasAudioEnabled=true, getRecordingId=1}
        at androidx.camera.video.Recorder$RecordingRecord.performOneTimeMediaMuxerCreation(Recorder.java:2391)
        at androidx.camera.video.Recorder.setupAndStartMediaMuxer(Recorder.java:1298)
        at androidx.camera.video.Recorder$2.onEncodedData(Recorder.java:1436)
        at androidx.camera.video.internal.encoder.EncoderImpl$MediaCodecCallback.lambda$sendEncodedData$3(EncoderImpl.java:937)
        at androidx.camera.video.internal.encoder.EncoderImpl$MediaCodecCallback$$ExternalSyntheticLambda6.run(Unknown Source:4)
        at androidx.camera.core.impl.utils.executor.SequentialExecutor$1.run(SequentialExecutor.java:111)
        at androidx.camera.core.impl.utils.executor.SequentialExecutor$QueueWorker.workOnQueue(SequentialExecutor.java:231)
        at androidx.camera.core.impl.utils.executor.SequentialExecutor$QueueWorker.run(SequentialExecutor.java:173)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
        at java.lang.Thread.run(Thread.java:923)
2021-12-15 11:56:00.395 19974-5309/? E/AndroidRuntime: FATAL EXCEPTION: pool-40-thread-2
    Process: com.pt.wshhp, PID: 19974
    java.lang.AssertionError: One-time media muxer creation has already occurred for recording RecordingRecord{getOutputOptions=MediaStoreOutputOptions{contentResolver=android.app.ContextImpl$ApplicationContentResolver@e6e1f6b, collectionUri=content://media/external/video/media, contentValues=_display_name=Worldstar-1639590959896, fileSizeLimit=0}, getCallbackExecutor=java.util.concurrent.Executors$FinalizableDelegatedExecutorService@850110f, getEventListener=com.pt.wshhp.viewmodels.RecordVideoViewModel$$ExternalSyntheticLambda0@fe4059c, hasAudioEnabled=true, getRecordingId=1}
        at androidx.camera.video.Recorder$RecordingRecord.performOneTimeMediaMuxerCreation(Recorder.java:2391)
        at androidx.camera.video.Recorder.setupAndStartMediaMuxer(Recorder.java:1298)
        at androidx.camera.video.Recorder$2.onEncodedData(Recorder.java:1436)
        at androidx.camera.video.internal.encoder.EncoderImpl$MediaCodecCallback.lambda$sendEncodedData$3(EncoderImpl.java:937)
        at androidx.camera.video.internal.encoder.EncoderImpl$MediaCodecCallback$$ExternalSyntheticLambda6.run(Unknown Source:4)
        at androidx.camera.core.impl.utils.executor.SequentialExecutor$1.run(SequentialExecutor.java:111)
        at androidx.camera.core.impl.utils.executor.SequentialExecutor$QueueWorker.workOnQueue(SequentialExecutor.java:231)
        at androidx.camera.core.impl.utils.executor.SequentialExecutor$QueueWorker.run(SequentialExecutor.java:173)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
        at java.lang.Thread.run(Thread.java:923)
2021-12-15 11:56:00.465 19974-5313/? E/AndroidRuntime: FATAL EXCEPTION: pool-40-thread-3
    Process: com.pt.wshhp, PID: 19974
    java.lang.AssertionError: One-time media muxer creation has already occurred for recording RecordingRecord{getOutputOptions=MediaStoreOutputOptions{contentResolver=android.app.ContextImpl$ApplicationContentResolver@e6e1f6b, collectionUri=content://media/external/video/media, contentValues=_display_name=Worldstar-1639590959896, fileSizeLimit=0}, getCallbackExecutor=java.util.concurrent.Executors$FinalizableDelegatedExecutorService@850110f, getEventListener=com.pt.wshhp.viewmodels.RecordVideoViewModel$$ExternalSyntheticLambda0@fe4059c, hasAudioEnabled=true, getRecordingId=1}
        at androidx.camera.video.Recorder$RecordingRecord.performOneTimeMediaMuxerCreation(Recorder.java:2391)
        at androidx.camera.video.Recorder.setupAndStartMediaMuxer(Recorder.java:1298)
        at androidx.camera.video.Recorder$2.onEncodedData(Recorder.java:1436)
        at androidx.camera.video.internal.encoder.EncoderImpl$MediaCodecCallback.lambda$sendEncodedData$3(EncoderImpl.java:937)
        at androidx.camera.video.internal.encoder.EncoderImpl$MediaCodecCallback$$ExternalSyntheticLambda6.run(Unknown Source:4)
        at androidx.camera.core.impl.utils.executor.SequentialExecutor$1.run(SequentialExecutor.java:111)
        at androidx.camera.core.impl.utils.executor.SequentialExecutor$QueueWorker.workOnQueue(SequentialExecutor.java:231)
        at androidx.camera.core.impl.utils.executor.SequentialExecutor$QueueWorker.run(SequentialExecutor.java:173)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
        at java.lang.Thread.run(Thread.java:923)

这些是崩溃亮点:

  • MIME type application/octet-stream cannot be inserted into content://media/external/video/media; expected MIME type under video/*

  • java.lang.AssertionError: One-time media muxer creation has already occurred for recording RecordingRecord{getOutputOptions=MediaStoreOutputOptions{contentResolver=android.app.ContextImpl$ApplicationContentResolver@e6e1f6b, collectionUri=content://media/external/video/media, contentValues=_display_name=VideoRecording-1639590959896, fileSizeLimit=0}, getCallbackExecutor=java.util.concurrent.Executors$FinalizableDelegatedExecutorService@850110f, getEventListener=com.videoapp.viewmodels.RecordVideoViewModel$$ExternalSyntheticLambda0@fe4059c, hasAudioEnabled=true, getRecordingId=1}

这是我迄今为止尝试过的(没有成功):

  • 在 ViewModel 中从 MediaModule.kt 创建对象,而不是注入它们
  • 使用 Activity 上下文而不是 Application 对象
  • 发生此崩溃时停止摄像头并重新启动(使用 try-catch)

似乎正在尝试向视频文件源录制两次。有没有人遇到过这个问题?在模拟器中录制很好,但在真实设备中却崩溃了。

【问题讨论】:

    标签: android kotlin video android-camera android-camerax


    【解决方案1】:

    嘿,我真的不知道你的问题,但我会寄给你我的 CameraX 在我的三星 A10s 中工作得非常好。我使用的是片段而不是活动,但它是相同的,你只改变上下文。

    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 android.util.Log
    import android.view.LayoutInflater
    import android.view.View
    import android.view.ViewGroup
    import android.widget.Toast
    import androidx.camera.core.*
    import androidx.camera.lifecycle.ProcessCameraProvider
    import androidx.camera.video.*
    import androidx.camera.video.VideoCapture
    import androidx.core.app.ActivityCompat
    import androidx.core.content.ContextCompat
    import androidx.core.content.PermissionChecker
    import androidx.fragment.app.Fragment
    import com.example.splashscreenkotlin.R
    import com.example.splashscreenkotlin.databinding.FragmentCameraBinding
    import java.lang.Exception
    import java.nio.ByteBuffer
    import java.text.SimpleDateFormat
    import java.util.*
    import java.util.concurrent.ExecutorService
    import java.util.concurrent.Executors
    
    typealias LumaListener = (luma: Double) -> Unit
    
    class CameraFragment : Fragment() {
    
        private var binding: FragmentCameraBinding? = null
        private val _binding get() = binding!!
    
        private var imageCapture: ImageCapture? = null
        private var videoCapture: VideoCapture<Recorder>? = null
        private var recording: Recording? = null
    
        private lateinit var cameraExecutorService: ExecutorService
    
        override fun onCreateView(
            inflater: LayoutInflater,
            container: ViewGroup?,
            savedInstanceState: Bundle?
        ): View {
            binding = FragmentCameraBinding.inflate(layoutInflater,container,false)
            return _binding.root
        }
    
        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
    
            // Request camera permissions
            if (allPermissionGranted()){
                startCamera()
            }else {
                ActivityCompat.requestPermissions(requireActivity(), REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS)
            }
    
            // Set up the listeners for take photo and video capture buttons
            _binding.imageCaptureButton.setOnClickListener { takePhoto() }
            _binding.videoCaptureButton.setOnClickListener { captureVideo() }
    
            cameraExecutorService = Executors.newSingleThreadExecutor()
        }
    
    
        private fun takePhoto(){
            //First, get a reference to the ImageCapture use case.
        // If the use case is null, exit out of the function.
        // This will be null If we tap the photo button before image capture is set up.
        // Without the return statement, the app would crash if it was null.
            val imageCapture = imageCapture ?: return
    
            //create a MediaStore content value to hold the image.
        // Use a timestamp so the display name in MediaStore will be unique.
            val name = SimpleDateFormat(FILENAME_FORMAT, Locale.getDefault()).format(System.currentTimeMillis())
            //This class is used to store a set of values that the ContentResolver can process.
            //ContentResolver class provides applications access to the content model.
            val contentValues = ContentValues().apply {
                //MediaStore is contract between the media provider and applications.
                // Contains definitions for the supported URIs and columns.
                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/SplashApp-Images")
                }
            }
    
            //Create an OutputFileOptions object.
        // This object is where we can specify things about how we want our output to be.
        // We want the output saved in the MediaStore so other apps could display it,
        // so add our MediaStore entry.
            val outputOptions = ImageCapture.OutputFileOptions
                .Builder(requireActivity().contentResolver,MediaStore.Images.Media.EXTERNAL_CONTENT_URI,contentValues)
                .build()
    
            //Call takePicture() on the imageCapture object.
        // Pass in outputOptions, the executor, and a callback for when the image is saved
            imageCapture.takePicture(outputOptions,ContextCompat.getMainExecutor(requireContext()),
            object : ImageCapture.OnImageSavedCallback{
    
                override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
                    val msg = "Photo capture succeeded: ${outputFileResults.savedUri}"
                    Toast.makeText(requireContext(),msg,Toast.LENGTH_SHORT).show()
                    Log.d(TAG,msg)
                }
    
                //In the case that the image capture fails or saving the image capture fails,
                // add in an error case to log that it failed.
                override fun onError(exception: ImageCaptureException) {
                    Log.e(TAG,"Photo Captured failed: ${exception.message}",exception)
                }
            })
        }
    
        private fun captureVideo(){
            //Check if the VideoCapture use case has been created: if not, do nothing.7
            val videoCapture = videoCapture ?: return
    
            //Disable the UI until the request action is completed by CameraX
            _binding.videoCaptureButton.isEnabled = false
    
            //If there is an active recording in progress, stop it and release the current recording.
        // We will be notified when the captured video file is ready to be used by our application.
            val currentRecording = recording
            if (currentRecording != null){
                currentRecording.stop()
                recording = null
                return
            }
    
            // we create our intended MediaStore video content object,
        // with system timestamp as the display name(so we could capture multiple videos).
            val name = SimpleDateFormat(FILENAME_FORMAT, Locale.getDefault()).format(System.currentTimeMillis())
            //ContentValue class is used to store a set of values that the ContentResolver can process.
            // ContentResolver class provides applications access to the content model.
            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/SplashApp-Videos")
                }
            }
    
            //Create a MediaStoreOutputOptions.Builder with the external content option.
            //MediaStoreOutputOptions class providing options for storing output to MediaStore.
            val mediaStoreOutputOptions = MediaStoreOutputOptions.Builder(
                requireActivity().contentResolver,MediaStore.Video.Media.EXTERNAL_CONTENT_URI
            )//Set the created video contentValues to the MediaStoreOutputOptions.Builder, and build our MediaStoreOutputOptions instance.
                .setContentValues(contentValues).build()
    
            //Configure the output option to the Recorder of VideoCapture<Recorder> and enable audio recording
            recording = videoCapture.output
                .prepareRecording(requireContext(),mediaStoreOutputOptions).apply {
                    if (PermissionChecker.checkSelfPermission(requireContext(),Manifest.permission.RECORD_AUDIO)
                        == PermissionChecker.PERMISSION_GRANTED) {
                        withAudioEnabled()
                    }
                }//Start this new recording, and register a lambda VideoRecordEvent listener.
                .start(ContextCompat.getMainExecutor(requireContext())){ recordEvent ->
    
                    when(recordEvent) {
                        //When the request recording is started by the camera device,
                            // toggle the "Start Capture" button text to say "Stop Capture".
                        is VideoRecordEvent.Start -> {
                            _binding.videoCaptureButton.apply {
                                text = getString(R.string.stop_capture)
                                isEnabled = true
                            }
                        }
                        //When the active recording is complete, notify the user with a toast,
                    // and toggle the "Stop Capture" button back to "Start Capture", and re-enable it
                        is VideoRecordEvent.Finalize -> {
                            _binding.videoCaptureButton.apply {
                                text = getString(R.string.start_capture)
                                isEnabled = true
                            }
                            if(!recordEvent.hasError()){
                                val msg = "Video Capture Succeed: " + "${recordEvent.outputResults.outputUri}"
                                Toast.makeText(requireContext(),msg,Toast.LENGTH_LONG).show()
                                Log.d(TAG,msg)
                            }else {
                                recording?.close()
                                recording = null
                                Log.e(TAG,"Video Capture Ends with an Error: " + "${recordEvent.error}")
                            }
                        }
                    }
                }
        }
    
        private fun startCamera(){
            // User ProcessCameraProvider Bcs A singleton which can be used to bind the lifecycle of cameras
            // to any LifecycleOwner within an application's process.
            // Retrieve it with getInstance
            // This eliminates the task of opening and closing the camera since CameraX is lifecycle-aware.
            val cameraFutureProvider = ProcessCameraProvider.getInstance(requireContext())
            //Registers a listener to be run  on the given executor.
            // The listener will run when the Future's computation is complete  or,
            // if the computation is already complete, immediately.
            cameraFutureProvider.addListener({
                // Used to bind the lifecycle of cameras to the lifecycle owner
                val cameraProvider: ProcessCameraProvider = cameraFutureProvider.get()
    
                // Preview use case that provides a camera preview stream for displaying on-screen.
                val preview = Preview.Builder().build().also {
                    // Decide how the surface we using
                    // get a surface provider from viewfinder, and then set it on the preview.
                    it.setSurfaceProvider(_binding.viewFinder.surfaceProvider)
                }
    
                val recorder = Recorder.Builder()
                    .setQualitySelector(QualitySelector.from(Quality.HIGHEST,
                        //Add a FallbackStrategy to the existing QualitySelector creation.
                        // This allows CameraX to pick up a supported resolution
                        // if the required Quality.HIGHEST is not supportable with the imageCapture use case.
                    FallbackStrategy.higherQualityOrLowerThan(Quality.SD)))
                    .build()
                videoCapture = VideoCapture.withOutput(recorder)
    
                imageCapture = ImageCapture.Builder().build()
    
                /*
                //ImageAnalysis use case providing CPU accessible images for an app to perform image analysis on.
                //ImageAnalysis acquires images from the camera via an ImageReader.
                // Each image is provided to an ImageAnalysis.Analyzer function which can be implemented by application code
                //where it can access image data for application analysis via an ImageProxy.
                val imageAnalyzer = ImageAnalysis.Builder().build().also {
                    it.setAnalyzer(cameraExecutorService,LuminosityAnalyzer { luma ->  
                        Log.d(TAG,"The Average luminosity: $luma")
                    })
                }
                */
    
    
                // Select back camera as a default
                val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
                try {
                    // Unbind use case before rebinding
                        // This will initiate a close of every currently open camera.
                    cameraProvider.unbindAll()
    
                    // Bind use cases to camera
                    cameraProvider.bindToLifecycle(
                        requireActivity(),cameraSelector,preview,imageCapture,videoCapture
                    )
                }catch (e:Exception){
                    Log.e(TAG, "Use case binding failed",e)
                }
            }// This returns an Executor that runs on the main thread.
                ,ContextCompat.getMainExecutor(requireContext()))
        }
    
        //The analyzer logs the average luminosity of the image.
        // To create an analyzer, we override the analyze function in a class that implements the ImageAnalysis.Analyzer interface.
        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 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)
                //The application is responsible for calling ImageProxy.close() to close the image.
                // Failing to close the image will cause future images to be stalled or dropped depending on the backpressure strategy.
                image.close()
            }
        }
    
        // Send Permission To access to camera
        private fun allPermissionGranted() = REQUIRED_PERMISSIONS.all {
            ContextCompat.checkSelfPermission(requireContext(),it) ==
                    PackageManager.PERMISSION_GRANTED
        }
    
        //Callback for the result from requesting permissions.
        // This method is invoked for every call on requestPermissions(String[], int).
        @Deprecated("Deprecated in Java")
        override fun onRequestPermissionsResult(
            requestCode: Int,
            permissions: Array<out String>,
            grantResults: IntArray
        ) {
            if (requestCode == REQUEST_CODE_PERMISSIONS){
                if (allPermissionGranted()){
                    startCamera()
                }else {
                    Toast.makeText(requireContext(),"Permission not granted by the user.",Toast.LENGTH_SHORT).show()
                    activity?.finish()
                }
            }
        }
    
    
        override fun onDestroyView() {
            super.onDestroyView()
            binding = null
            cameraExecutorService.shutdown()
        }
    
    
    
        companion object {
            private const val TAG = "CAMERAX"
            private const val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SS"
            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()
        }
    }
    

    对于我的 FragmentLayout:

    <?xml version="1.0" encoding="utf-8"?>
    <FrameLayout 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=".fragments.CameraFragment">
    
        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:id="@+id/camera_container"
            android:background="@android:color/black"
            android:layout_height="match_parent">
    
            <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>
    
    </FrameLayout>
    

    顺便说一句,使用bottomNavigation 工作很棒:) 即使我更改片段,我的相机片段也不会崩溃

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-04-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多