我有一个现有的 Camera2 应用程序,我想将其迁移到 CameraX 以修复某些 Android 设备上的一些怪癖(三星崩溃了几次)。
目前,我有一个自定义
VideoPipeline
,我可以在其中执行以下操作:
ImageReader
,我可以使用 CPU 访问相机框架并在其上运行面部检测ImageWriter
,或通过 HardwareBuffer
作为 OpenGL 纹理传递)在 Camera2 中,我刚刚将从
surface
(第 1 步)获得的 ImageReader
连接到相机,它开始以我将 ImageReader
配置为以(对于 CPU 为 ImageFormat.YUV_420_888
)运行的任何格式流式传输到我的 ImageReader算法或 ImageFormat.PRIVATE
用于 GPU 算法)。
VideoPipeline.kt
我一直在尝试了解 CameraX 对整个相机管道的相当高级的抽象,但我似乎无法理解如何创建一个自定义视频管道,在那里我可以做我在 Camera2 中所做的事情。
到目前为止,这就是我的想法:
class VideoPipeline(
private val format = ImageFormat.YUV_420_888, // or ImageFormat.PRIVATE
private val callback: CameraSession.Callback
) : VideoOutput, Closeable {
companion object {
private const val MAX_IMAGES = 3
private const val TAG = "VideoPipeline"
}
// Output
// TODO: Use `Recording`/`Recorder` output from CameraX?
// TODO: When I didn't override getMediaSpec, CameraX crashed.
@SuppressLint("RestrictedApi")
override fun getMediaSpec(): Observable<MediaSpec> {
val mediaSpec = MediaSpec.builder().setOutputFormat(MediaSpec.OUTPUT_FORMAT_MPEG_4).configureVideo { video ->
// TODO: Instead of hardcoding that, can I dynamically get those values from the Camera?
video.setFrameRate(Range(0, 60))
video.setQualitySelector(QualitySelector.from(Quality.HD))
}
return ConstantObservable.withValue(mediaSpec.build())
}
@SuppressLint("RestrictedApi")
override fun onSurfaceRequested(request: SurfaceRequest) {
val size = request.resolution
val surfaceSpec = request.deferrableSurface
Log.i(TAG, "Creating $size Surface... (${request.expectedFrameRate.upper} FPS, expected format: ${surfaceSpec.prescribedStreamFormat}, ${request.dynamicRange})")
// Create ImageReader
val imageReader = ImageReader.newInstance(size.width, size.height, format, MAX_IMAGES)
imageReader.setOnImageAvailableListener({ reader ->
val image = reader.acquireNextImage() ?: return@setOnImageAvailableListener
// 1. Detect Faces
val faces = callback.detectFaces(image)
// 2. Forward to OpenGL
onFrame(image)
}, CameraQueues.videoQueue.handler)
// Pass the ImageReader surface to CameraX when bound to a lifecycle
request.provideSurface(imageReader.surface, CameraQueues.videoQueue.executor) { result ->
imageReader.close()
}
}
}
然后设置相机:
val fpsRange = Range(30, 30)
val preview = Preview.Builder().build()
preview.setSurfaceProvider(previewView.surfaceProvider)
val videoPipeline = VideoPipeline(ImageFormat.YUV_420_888, callback)
val video = VideoCapture.Builder(videoPipeline).also { video ->
video.setMirrorMode(MirrorMode.MIRROR_MODE_ON_FRONT_ONLY)
video.setTargetFrameRate(fpsRange)
}.build()
val camera = provider.bindToLifecycle(this, cameraSelector, preview, video)
..但是这会崩溃,因为在调用
acquireNextImage()
时由于某种原因,因为显然表面生成器(相机)配置为格式0x1
(RGB)而不是0x23
(YUV)?:
java.lang.UnsupportedOperationException: The producer output buffer format 0x1 doesn't match the ImageReader's configured buffer format 0x23.
at android.media.ImageReader.nativeImageSetup(Native Method)
at android.media.ImageReader.acquireNextSurfaceImage(ImageReader.java:439)
at android.media.ImageReader.acquireNextImage(ImageReader.java:493)
at com.mrousavy.camera.core.VideoPipeline.onSurfaceRequested$lambda$1(VideoPipeline.kt:99)
at com.mrousavy.camera.core.VideoPipeline.$r8$lambda$5heGRS9RL_Z-x-BiU-ziCBMS68U(Unknown Source:0)
at com.mrousavy.camera.core.VideoPipeline$$ExternalSyntheticLambda1.onImageAvailable(Unknown Source:2)
at android.media.ImageReader$ListenerHandler.handleMessage(ImageReader.java:800)
at android.os.Handler.dispatchMessage(Handler.java:112)
at android.os.Looper.loop(Looper.java:216)
at android.os.HandlerThread.run(HandlerThread.java:65)
现在尤其是因为
getMediaSpec()
方法,我不确定我正在做的是否是正确的方法,有人有任何想法或想法吗?关于此方法的多个问题:
VideoOutput
然后在 VideoCapture.Builder
中使用它,还是应该直接扩展 UseCase
并自己做所有事情?Recorder
中的 CameraX' Recording
/VideoPipeline
实例从 OpenGL 流式传输到该 Surface 吗?我不想重写整个 MediaMuxer/MediaEncoder 部分...请查看OverlayEffect API。它允许应用程序缓冲 GPU 流并等待 CPU 流的结果,然后再在 GPU 流之上渲染覆盖。至于CPU流,您可以使用ImageAnalysis API获取。不确定今天如何检测人脸,但您可以使用 ML Kit 和 MLKitAnalyzer API 获取人脸检测结果。
对于代码示例,您可以查看此原型更改。 SyncedOverlayEffect展示了如何同步CPU流与GPU流。 OverlayFragment展示了如何使用MLKitAnalyzer获取检测结果。