CameraX 自定义 OpenGL 视频管道(`UseCase`/`VideoOutput`)

问题描述 投票:0回答:1

我有一个现有的 Camera2 应用程序,我想将其迁移到 CameraX 以修复某些 Android 设备上的一些怪癖(三星崩溃了几次)。

目前,我有一个自定义

VideoPipeline
,我可以在其中执行以下操作:

  1. 使用
    ImageReader
    ,我可以使用 CPU 访问相机框架并在其上运行面部检测
  2. 检测到人脸后,将帧转发到我的 OpenGL 管道(通过
    ImageWriter
    ,或通过
    HardwareBuffer
    作为 OpenGL 纹理传递)
  3. 在 OpenGL 管道中,我使用 OpenGL 在用户脸部周围绘制一个框。
  4. 在脸部周围绘制方框后,我将所有内容渲染到两个输出表面中:
    1. 预览视图表面
    2. MediaRecorder/编码器表面(录制到 .mp4 时)

在 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()
方法,我不确定我正在做的是否是正确的方法,有人有任何想法或想法吗?关于此方法的多个问题:

  1. 我应该扩展
    VideoOutput
    然后在
    VideoCapture.Builder
    中使用它,还是应该直接扩展
    UseCase
    并自己做所有事情?
  2. 我可以以某种方式重用我的
    Recorder
    中的 CameraX'
    Recording
    /
    VideoPipeline
    实例从 OpenGL 流式传输到该 Surface 吗?我不想重写整个 MediaMuxer/MediaEncoder 部分...
android opengl-es android-camera2 android-camerax
1个回答
0
投票

请查看OverlayEffect API。它允许应用程序缓冲 GPU 流并等待 CPU 流的结果,然后再在 GPU 流之上渲染覆盖。至于CPU流,您可以使用ImageAnalysis API获取。不确定今天如何检测人脸,但您可以使用 ML KitMLKitAnalyzer API 获取人脸检测结果。

对于代码示例,您可以查看此原型更改SyncedOverlayEffect展示了如何同步CPU流与GPU流。 OverlayFragment展示了如何使用MLKitAnalyzer获取检测结果。

© www.soinside.com 2019 - 2024. All rights reserved.