簡體   English   中英

CameraX 視頻捕獲不記錄在物理設備上

[英]CameraX Video capture not recording on physical device

你好。 我對 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) }
  • 記錄ViewModel(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 的真實設備(三星 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 創建對象而不是注入它們
  • 使用活動上下文而不是應用程序 object
  • 發生崩潰時停止相機並重新啟動它(使用 try-catch)

似乎正在嘗試錄制兩次到視頻文件源。 有沒有人遇到過這個? 在模擬器中錄制得很好,但在真實設備中卻崩潰了。

嘿,我真的不知道你的問題,但我會發給你我的 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 一起工作很棒 :) 即使我更改片段,我的相機片段也不會崩潰

我在華為p30和華為p10有同樣的問題。我嘗試將相機切換到前置並且它可以工作,但是當我切換到后置攝像頭時,它

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM