前言
话接上文,本篇将要学习如何获取预览流,有了预览流我们可以做很多场景,如人形、人脸、车牌识别,如推流到流媒体服务器等等。
如果感到不适,强烈建议从CameraX 一看过来
布局文件
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/h264Record"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="录制h264" />
<Button
android:id="@+id/disableH264Record"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:text="结束录制" />
</LinearLayout>
<androidx.camera.view.PreviewView
android:id="@+id/previewView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
提供了两个按钮,分别用于开始录制和结束录制,PreviewView用于显示预览界面
绑定相机到应用程序
private fun initCamera() {
cameraProviderFuture = ProcessCameraProvider.getInstance(this)
//请求 CameraProvider 后,请验证它能否在视图创建后成功初始化。以下代码展示了如何执行此操作:
cameraProviderFuture.addListener(Runnable {
val cameraProvider = cameraProviderFuture.get()
bindPreview(cameraProvider)
}, ContextCompat.getMainExecutor(this))
}
@SuppressLint("RestrictedApi", "UnsafeOptInUsageError")
fun bindPreview(cameraProvider: ProcessCameraProvider) {
//预览
var preview: Preview = Preview.Builder()
.build()
// 选择相机
var cameraSelector: CameraSelector = CameraSelector.Builder()
.requireLensFacing(CameraSelector.LENS_FACING_BACK) //后置
.build()
// 提供previewView预览控件
preview.setSurfaceProvider(previewView.surfaceProvider)
// 图片分析
imageAnalysis = ImageAnalysis.Builder()
.setTargetResolution(size) //设置分辨率
.setBackpressureStrategy(ImageAnalysis.STRATEGY_BLOCK_PRODUCER) //阻塞模式,setAnalyzer过于耗时会导致预览卡顿
.setTargetRotation(Surface.ROTATION_90)
.build()
// 在绑定之前 你应该先解绑
cameraProvider.unbindAll()
var camera = cameraProvider.bindToLifecycle(
this as LifecycleOwner,
cameraSelector,
imageAnalysis,
preview
)
}
setBackpressureStrategy方法支持阻塞和非阻塞的方式获取帧,他们的区别介绍如下:
开始录制/结束录制
/**
* 结束录制
*/
private fun customDisableStopRecording() {
//停止h264编码
avcEncoder?.stop()
avcEncoder = null
//取消分析器
imageAnalysis!!.clearAnalyzer()
}
/**
* 开始录制
*/
private fun customRecording() {
//保存为h264
val file = File(
getExternalFilesDir(Environment.DIRECTORY_MOVIES)!!.path,
"${System.currentTimeMillis()}.h264"
)
if (!file.exists()) {
file.createNewFile()
}
imageAnalysis?.let {
it.setAnalyzer(mainExecutor, ImageAnalysis.Analyzer { image ->
if (avcEncoder == null) {
avcEncoder = AvcEncoder()
//如果设置的默认分辨率不支持,则使用自动选择的分辨率,否则编码器将会报错
if (image.width != size.width) {
Log.d(TAG, "customRecording: 不支持默认分辨率,新的分辨率为:${image.width }")
size = Size(image.width,image.height)
}
avcEncoder?.config(size, file)
avcEncoder?.start()
}
Log.d(TAG, "width: ${image.width}")
Log.d(TAG, "height: ${image.height}")
//编码为h624
avcEncoder?.addPlanes(ImageUtil.yuv_420_888toNv21(image))
image.close()
})
}
}
由于得到的图片格式是YUV_420_888的,这里我采用先转为NV21再转为NV12然后编码H264
图片分析得到的数据是不会自动处理旋转的,所以我们需要再处理旋转角度,setTargetRotation(Surface.ROTATION_90)是目标角度,不代表输出数据角度。
分辨率我们可以设置一个默认的值如Size(1920, 1080),当然你的设备可能不支持,会自动降低,所以编码时需要做好处理。上面提到的代码已有处理。可以正常编码。
注意:ImageAnalysis和VideoCapture不可以同时绑定。
如果你下载了Demo,结束录制之后,依然在外部存储目录下会生成.h264的文件,可以使用VLC播放器来验证是否录制成功。