Android 音视频开发第3弹 - CameraX 图像视频采集

CameraX 是一个用于 Android 相机开发的 Jetpack 组件,它简化了相机功能的实现过程,并提供了一套一致的 API 接口,支持搭载 Android 5.0 及以上的设备,确保各设备间的一致性,支持大多数常见的相机用例,例如预览,图片拍摄,图片分析,视频拍摄等。

添加依赖
 

val cameraxVersion = "1.2.1"
implementation("androidx.camera:camera-core:${cameraxVersion}")
implementation("androidx.camera:camera-camera2:${cameraxVersion}")
implementation("androidx.camera:camera-lifecycle:${cameraxVersion}")
implementation("androidx.camera:camera-video:${cameraxVersion}")
implementation("androidx.camera:camera-view:${cameraxVersion}")
implementation("androidx.camera:camera-extensions:${cameraxVersion}")

需要的权限如下:

<uses-feature
    android:name="android.hardware.camera"
    android:required="false" />

<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

这些权限需要动态申请的,这里不再赘述。其中,<uses-feature> 标签用于声明应用程序所需要的硬件或软件功能,这里指相机功能,required 属性指定应用程序是否对该功能的要求是必须的,false 表示相机功能是可选的。

预览

预览使用 PreviewView,这是一种可以剪裁,缩放和旋转以确保正确显示的 View,当相机处于活动状态时,图片预览会流式传输到 PreviewView 中的 Surface。

添加布局

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

请求 ProcessCameraProvider,选择相机并绑定生命周期和用例即可,代码如下:

class CameraActivity : AppCompatActivity() {

    private lateinit var cameraProviderFuture: ListenableFuture<ProcessCameraProvider>
    private lateinit var binding: ActivityCameraBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_camera)

        // 请求 CameraProvider,并验证它能否在视图创建后成功初始化。
        cameraProviderFuture = ProcessCameraProvider.getInstance(this)
        cameraProviderFuture.addListener({
            val cameraProvider = cameraProviderFuture.get()
            val preview = Preview.Builder().build()
            val cameraSelector =
                CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build()
            preview.setSurfaceProvider(binding.previewView.surfaceProvider)
            // 绑定生命周期和用例
            cameraProvider.bindToLifecycle(this as LifecycleOwner, cameraSelector, preview)
        }, ContextCompat.getMainExecutor(this))

    }

}

图片拍摄

在上面的代码中 bindToLifecycle 添加个 ImageCapture 参数。

private var imageCapture: ImageCapture? = null

imageCapture = ImageCapture.Builder().build()
cameraProvider.bindToLifecycle(
    this as LifecycleOwner,
    cameraSelector,
    imageCapture,
    preview
)

然后执行拍照方法,将图片保存在相册中即可,拍照代码如下:

private fun takePhoto() {
    //创建用于保存图片的 MediaStore 内容值,这里使用时间戳,确保 MediaStore 中的显示名是唯一的。
    val contentValues = ContentValues().apply {
        put(MediaStore.MediaColumns.DISPLAY_NAME, "img_${System.currentTimeMillis()}")
        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 中。
    val outputOptions = ImageCapture.OutputFileOptions
        .Builder(
            contentResolver,
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
            contentValues
        )
        .build()
    // 拍照
    imageCapture?.takePicture(
        outputOptions,
        ContextCompat.getMainExecutor(this@CameraActivity),
        object : ImageCapture.OnImageSavedCallback {
            override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
                Log.i(TAG, "onImageSaved")
            }

            override fun onError(exception: ImageCaptureException) {
                Log.i(TAG, "onError: ${exception.message}")
            }

        })
}

图片分析

可以使用 ImageAnalysis 进行图片分析,实现 ImageAnalysis.Analyzer 接口的类中的 analyze 函数。

private class MyImageAnalyzer : ImageAnalysis.Analyzer {
    override fun analyze(image: ImageProxy) { // 在这里编写图像分析的具体逻辑,这里做个简单演示。
        // 宽高
        val imageWidth = image.width
        val imageHeight = image.height
        // 根据需要处理每个平面的图像数据
        image.planes.forEach {
            // 获取图像平面的行跨度,即相邻两行之间的字节偏移量。
            val rowStride = it.rowStride
            // 获取图像平面的像素跨度,即相邻两个像素之间的字节偏移量。
            val pixelStride = it.pixelStride
            // 获取图像平面的数据缓冲区,可以通过该缓冲区读取或写入图像数据。
            val buffer = it.buffer
            // buffer.remaining 返回剩余可读取或写入的字节数。
            val byteArray = ByteArray(buffer.remaining())
            // 转化为字节数组
            buffer.get(byteArray)
            // 处理完图像后,释放资源。
            buffer.clear()
        }
        image.close()
    }
}

其中,planes 是一个数组,其中包含了多个图像平面。对于彩色图像,通常会有三个平面,分别对应红色,绿色和蓝色通道。您可以通过 planes[0],planes[1],planes[2] 等进行访问。

有些人可能会问:什么是图像平面?其实,在相机图像捕获过程中,图像会以多个平面的方式存储,这种存储方式称为平面布局,每个平面都包含了图像数据的一部分。对于彩色图像,常见的平面布局是 YUV 或 RGBA。说到这俩大家应该就清楚了,在处理相机图像时,需要根据具体的平面布局,将图像数据从每个平面提取出来,并进行相应的处理。

当使用 image.planes 来获取图像数据时,每个平面都包含一个字节缓冲区。例如,对于 RGBA 平面布局,image.planes[0].buffer 是指 R 平面的数据缓冲区,存储了图像的红色通道信息,对于 YUV 平面布局,image.planes[0].buffer 通常是 Y 平面的数据缓冲区,存储了图像的亮度信息。

CameraX 可通过 setOutputImageFormat,支持 YUV_420_888 和 RGBA_8888。默认格式为 YUV_420_888。

最后,将分析器设置进去即可,如下所示:

val imageAnalyzer = ImageAnalysis.Builder()
    .build()
    .also {
        it.setAnalyzer(ContextCompat.getMainExecutor(this), MyImageAnalyzer())
    }
cameraProvider.bindToLifecycle(
    this as LifecycleOwner,
    cameraSelector,
    imageCapture,
    imageAnalyzer,
    preview
)

视频拍摄

捕获系统通常会录制视频流和音频流,对其进行压缩,对这两个流进行多路复用,然后将生成的流写入磁盘。

视频拍摄使用 VideoCapture,同样,我们需要将其绑定到 Lifecycle,如下所示:

cameraProviderFuture = ProcessCameraProvider.getInstance(this)
cameraProviderFuture.addListener({
    val cameraProvider = cameraProviderFuture.get()
    val preview = Preview.Builder().build()
    val cameraSelector =
        CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build()
    preview.setSurfaceProvider(binding.previewView.surfaceProvider)
  
    val recorder = Recorder.Builder()
        .setQualitySelector(QualitySelector.from(Quality.HIGHEST))
        .build()
    videoCapture = VideoCapture.withOutput(recorder)

    cameraProvider.bindToLifecycle(
        this as LifecycleOwner,
        cameraSelector,
        preview,
        videoCapture
    )
}, ContextCompat.getMainExecutor(this))

视频拍摄方法如下:

private fun takeVideo() {
    // 如果有正在进行的录制操作,请将其停止并释放当前的 recording
    val curRecording = recording
    if (curRecording != null) {
        curRecording.stop()
        recording = null
        return
    }
    val contentValues = ContentValues().apply {
        put(MediaStore.MediaColumns.DISPLAY_NAME, "video_${System.currentTimeMillis()}")
        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 实例
    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@CameraActivity,
                Manifest.permission.RECORD_AUDIO
            ) == PermissionChecker.PERMISSION_GRANTED
        ) {
            // 启用音频
            withAudioEnabled()
        }
    }?.start(ContextCompat.getMainExecutor(this)) {
        when (it) {
            is VideoRecordEvent.Start -> {
                Log.i(TAG, "indicates the start of recording")
            }

            is VideoRecordEvent.Finalize -> {
                if (!it.hasError()) {
                    Log.d(TAG, "Video capture succeeded: ${it.outputResults.outputUri}")
                } else {
                    recording?.close()
                    recording = null
                    Log.e(TAG, "Video capture ends with error: ${it.error}")
                }
            }
        }
    }
}

调用该方法即可进行视频采集了,录制的是 mp4 文件,如果想要停止录制,调用如下:

recording?.stop()
recording = null

原文 Android 音视频开发第3弹 - CameraX 图像视频采集

★文末名片可以免费领取音视频开发学习资料,内容包括(FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)以及音视频学习路线图等等。

见下方!↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android音视频开发是指在Android平台上进行音频和视频相关功能的开发。它涉及到音频的录制、播放,视频采集、编码、解码和播放等方面。 在Android音视频开发中,可以使用Android提供的多媒体框架来实现各种功能。以下是Android音视频开发的一些关键点: 1. 音频开发- 音频录制:可以使用AudioRecord类进行音频的录制,通过设置音频源、采样率、声道数等参数来实现。 - 音频播放:可以使用MediaPlayer类或AudioTrack类进行音频的播放,通过设置音频文件路径或音频数据来实现。 2. 视频开发- 视频采集:可以使用Camera类或Camera2 API进行视频采集,通过设置摄像头参数、预览尺寸等来实现。 - 视频编码:可以使用MediaCodec类进行视频的编码,通过设置编码器类型、编码参数等来实现。 - 视频解码:可以使用MediaCodec类进行视频的解码,通过设置解码器类型、解码参数等来实现。 - 视频播放:可以使用SurfaceView或TextureView进行视频的播放,通过设置视频文件路径或视频数据来实现。 3. 音视频处理: - 音频处理:可以使用AudioEffect类进行音频的处理,如混音、变声等。 - 视频处理:可以使用OpenGL ES进行视频的处理,如滤镜、特效等。 4. 直播和推流: - 直播:可以使用第三方库,如librtmp、FFmpeg等来实现音视频的直播功能。 - 推流:可以使用第三方库,如librtmp、FFmpeg等来实现音视频的推流功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值