使用CameraX(Android Jetpack)可以录制视频吗?

32

谷歌已经发布了作为Jetpack一部分的新CameraX库。它看起来非常适合拍照,但我的用例还需要制作视频。我尝试通过谷歌搜索相关信息,但没有找到任何内容。

所以,使用CameraX Jetpack库是否可以录制视频呢?

6个回答

26

可以使用CameraX录制视频。我已经尝试过使用CameraX的Github演示来实现录制视频。请参考下面的代码,希望能对您有所帮助。

CameraX中的视频配置:

val videoCaptureConfig = VideoCaptureConfig.Builder().apply {
    setLensFacing(lensFacing)
    setTargetAspectRatio(screenAspectRatio)
    setTargetRotation(viewFinder.display.rotation)

}.build()

videoCapture = VideoCapture(videoCaptureConfig)

CameraX.bindToLifecycle(this, preview, imageCapture, videoCapture)

开始录制视频:

videoCapture?.startRecording(videoFile, object : VideoCapture.OnVideoSavedListener {
        override fun onVideoSaved(file: File?) {
            Log.i(javaClass.simpleName, "Video File : $file")
        }

        override fun onError(useCaseError: VideoCapture.UseCaseError?, message: String?, cause: Throwable?) {
            Log.i(javaClass.simpleName, "Video Error: $message")
        }

    })

停止视频录制:

videoCapture?.stopRecording()

我在Github的评论中提到了同样的问题:https://github.com/android/camera/issues/2#issuecomment-490773932

注意:使用CameraX实现视频录制的代码可能与上述代码有所不同。因为这段代码是我独立开发的,没有参考其他任何资料,只有Github Demo。

请查看Oscar Wahltinez于2019年5月14日发表的重要评论


15
请注意,目前API中视频录制用例已标记为隐藏状态,并且处于非常初步的阶段,可能会发生变化。有关公开可用类,请参阅javadocs(https://developer.android.com/reference/androidx/camera/core/package-summary.html)。 - Oscar Wahltinez
5
不要忘记添加RECORD_AUDIO权限。 - Sergey Stasishin
嗯...看起来你不能从Java使用它,或者可能是版本的问题。VideoCapture类被标记为@RestrictTo(Scope.LIBRARY_GROUP),这会阻止其实例化。 - Fran Marzoa
请分享XML文件。很难理解视频预览显示在哪个组件上。 - rohegde7
@rohegde7 你可以查看官方的CameraX示例,其中包含完整的XML演示。 - Patel Pinkal

9

更新 Patel Pinkal 的答案。 由于已经发布了 beta 版本,我们不能再使用 VideoCaptureConfig.Builder(),相反,你可以采用以下方式:

 videoCapture = VideoCapture.Builder().apply {
     // init config here
 }.build()

9

这是我的解决方案

//Versions in Gradle
def camerax_version = "1.0.0-beta06"
def camera_extensions = "1.0.0-alpha13"

private lateinit var videoCapture: VideoCapture
private lateinit var viewFinder: PreviewView
private lateinit var outputDirectory: File
private var lensFacing: Int = CameraSelector.LENS_FACING_FRONT


private val executor = Executors.newSingleThreadExecutor()
private var isRecording = false
private var camera: Camera? = null
private lateinit var cameraProviderFuture: ListenableFuture<ProcessCameraProvider>


//onCreate
viewFinder = preview_video_view
    runWithPermissions(*permissions) {
        startCamera(view.context)
        initClicks()
    }

 @SuppressLint("RestrictedApi", "UnsafeExperimentalUsageError")
private fun startCamera(context: Context) {
    outputDirectory = getOutputDirectory(context)
    cameraProviderFuture = ProcessCameraProvider.getInstance(context)

    val cameraSelector = CameraSelector.Builder().requireLensFacing(lensFacing).build()

    // Create a configuration object for the video use case
    val videoCaptureConfig = VideoCaptureConfig.Builder().apply {
        setTargetRotation(viewFinder.display.rotation)
        setCameraSelector(cameraSelector)
    }


    //CameraX.initialize(context, this.cameraXConfig) 

    videoCapture = VideoCapture(videoCaptureConfig.useCaseConfig)

    val preview: Preview = Preview.Builder().apply {
        setTargetAspectRatio(AspectRatio.RATIO_16_9)
        setTargetRotation(viewFinder.display.rotation)
    }.build()
    preview.setSurfaceProvider(viewFinder.createSurfaceProvider())

    cameraProviderFuture.addListener(Runnable {
        val cameraProvider = cameraProviderFuture.get()
        camera = cameraProvider.bindToLifecycle(
            viewLifecycleOwner,
            cameraSelector,
            preview,
            videoCapture
        )
        
    }, ContextCompat.getMainExecutor(context))
}

@SuppressLint("RestrictedApi")
private fun startRecording() {
    val file = createFile(
        outputDirectory,
        FILENAME,
        VIDEO_EXTENSION
    )

    videoCapture.startRecording(
        file,
        executor,
        object : VideoCapture.OnVideoSavedCallback {
            override fun onVideoSaved(file: File) {
                Handler(Looper.getMainLooper()).post {
                    showMessage(file.name + " is saved")
                }
            }

            override fun onError(videoCaptureError: Int, message: String, cause: Throwable?) {
                Handler(Looper.getMainLooper()).post {
                    showMessage(videoCaptureError.toString() + " " + message)
                }
            }
        }
    )
}

@SuppressLint("RestrictedApi")
private fun stopRecording() {
    videoCapture.stopRecording()
}

 override fun getCameraXConfig(): CameraXConfig {
    return Camera2Config.defaultConfig()
}

companion object {
    private const val FILENAME = "yyyy_MM_dd_HH_mm_ss"
    private const val VIDEO_EXTENSION = ".mp4"

    private val permissions = arrayOf(
        Manifest.permission.WRITE_EXTERNAL_STORAGE,
        Manifest.permission.READ_EXTERNAL_STORAGE,
        Manifest.permission.CAMERA,
        Manifest.permission.RECORD_AUDIO
    )

    fun getOutputDirectory(context: Context): File {
        val appContext = context.applicationContext
        val mediaDir = appContext.externalMediaDirs.firstOrNull()?.let {
            File(it, appContext.resources.getString(R.string.app_name)).apply { mkdirs() }
        }
        return if (mediaDir != null && mediaDir.exists()) mediaDir else appContext.filesDir
    }

    fun createFile(baseFolder: File, format: String, extension: String) =
        File(baseFolder, SimpleDateFormat(format, Locale.US)
            .format(System.currentTimeMillis()) + extension)
}

你好,能帮我一下吗?请问在你的代码中我应该在哪里添加视频时长? - unzila

6
截至 2021 年 4 月。
val videoCapture = VideoCapture.Builder().build()
val outputDirectory = getOutputDirectory()


fun getOutputDirectory(): File {
        val mediaDir = externalMediaDirs.firstOrNull()?.let {
            File(it, resources.getString(R.string.app_name)).apply { mkdirs() } }
        return if (mediaDir != null && mediaDir.exists())
            mediaDir else filesDir
}


@SuppressLint("RestrictedApi")
    private fun startRecording() {
        val videoFile = File(
            outputDirectory,
            SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-SSS", Locale.US
            ).format(System.currentTimeMillis()) + ".mp4")
        val outputOptions = VideoCapture.OutputFileOptions.Builder(videoFile).build()

        videoCapture?.startRecording(outputOptions, ContextCompat.getMainExecutor(this), object: VideoCapture.OnVideoSavedCallback {
            override fun onError(videoCaptureError: Int, message: String, cause: Throwable?) {
                Log.e(TAG, "Video capture failed: $message")
            }

            override fun onVideoSaved(outputFileResults: VideoCapture.OutputFileResults) {
                val savedUri = Uri.fromFile(videoFile)
                val msg = "Video capture succeeded: $savedUri"
                Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
                Log.d(TAG, msg)
            }
        })
    }

@SuppressLint("RestrictedApi")
    private fun stopRecording() {
        videoCapture?.stopRecording()
    }

请注意,该 API 仍然受到限制,并且可能会发生更改。

有没有办法在录制视频的同时播放捕获的视频? - Cerulean.Source

1

0
关于Sergei的回答,videoCapture.startRecording() 接受 VideoCapture.OutputFileOptions 而不是 file,对于 camerax_version = '1.0.0-rc01' 版本应该这样使用:
videoCapture.startRecording(
        VideoCapture.OutputFileOptions.Builder(file).build(),
        executor,
        object : VideoCapture.OnVideoSavedCallback {
            override fun onVideoSaved(outputFileResults: VideoCapture.OutputFileResults) {
                TODO("Not yet implemented")
            }

            override fun onError(videoCaptureError: Int, message: String, cause: Throwable?) {
                TODO("Not yet implemented")
            }
        }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接