我该如何在MediaCodec编码器和CameraX之间共享Surface?

5
我希望能够从CameraX(预览Use case)中获取图像,并使用MediaCodec将它们编码为h.264视频。我如何实现这个目标?我尝试的方法是,使用MediaCodec.createInputSurface()返回的Surface,在Preview.Builder()中使用Preview.setSurfaceProvider(),并继承自Preview.SurfaceProvider类,然后在此设置和配置我的编码器,并覆盖onSurfaceRequested()以从createInputSurface()返回Surface。这样做可以成功吗?我真的可以像这样共享一个Surface,并期望CameraX将数据写入这个Surface并填充给我的Encoder吗?
有没有更有效的方法来编码实时CameraX的视频流?
注意:我正在使用KOTLIN
1个回答

2
我用 CameraX 的 OpenGL 测试中的 OpenGLRenderer 终于解决了这个问题。这适用于 CameraX 的 beta 7 版本。
像往常一样设置 CameraX,但使用两个预览。
val preview: Preview = Preview.Builder().apply {
    setTargetResolution(targetSize)
    setTargetRotation(rotation)
}.build()

val encoderPreview: Preview = Preview.Builder().apply {
    setTargetResolution(targetSize)
    setTargetRotation(rotation)
}.build()

cameraProvider.unbindAll()

camera = cameraProvider.bindToLifecycle(
        lifecycleOwner,
        cameraSelector,
        preview,
        encoderPreview
)

preview.setSurfaceProvider(viewFinder.createSurfaceProvider())

然后初始化编码器:
val format = MediaFormat.createVideoFormat(
        "video/avc", resolution.width, resolution.height
)

format.setInteger(
        MediaFormat.KEY_COLOR_FORMAT,
        MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface
)

format.setInteger(MediaFormat.KEY_BIT_RATE, 500 * 1024)
format.setInteger(MediaFormat.KEY_FRAME_RATE, 25)
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 3)

encoder = MediaCodec.createEncoderByType("video/avc")

encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)

"并连接两者:"
private val glRenderer = OpenGLRenderer()

surface = encoder.createInputSurface()

glRenderer.attachInputPreview(encoderPreview)

glRenderer.setFrameUpdateListener(executor, Consumer<Long> {
    // when frame is written to output surface
    publishFrame()
})

encoder.start()

glRenderer.attachOutputSurface(surface, resolution, 0)

发布帧函数:
private fun publishFrame() {
    val index: Int = try {
        encoder.dequeueOutputBuffer(info, 10 * 1000)
    } catch (e: Exception) {
        -1
    }

    if (!isRunning.get()) {
        return
    }

    if (index >= 0) {
        val outputBuffer = encoder.getOutputBuffer(index)
        if (outputBuffer == null) {
            return
        }

        if (info.size > 0) {
            outputBuffer.position(info.offset)
            outputBuffer.limit(info.offset + info.size)
            info.presentationTimeUs = System.nanoTime() / 1000

            // do something with frame
        }

        encoder.releaseOutputBuffer(index, false)

        if (info.flags.hasFlag(MediaCodec.BUFFER_FLAG_END_OF_STREAM)) {
            return
        }
    }
}

请注意,编码器中的FRAME_RATE参数不受尊重,您将根据发布到输出表面的帧数(调用publishFrame的次数)获得帧速率。要控制帧速率,请更改OpenGLRenderer中的private void renderLatest()函数(丢弃帧,不要调用renderTexture)。
编辑:关于Camerax谷歌小组的对话中提出的更新解决方案可以在此处找到

谢谢您。连接两者时,编码器预览在哪里使用?我看到您在glRenderer.attachInputPreview(preview)中使用了preview。如果您有示例,我一直很难让它与我的编码器配合工作,屏幕只是黑色的。 - nymeria
1
嗨!我犯了一个错误。在glRenderer.attachInputPreview中应该是encoderPreview。已经进行了修复。 - zoki
1
我不再使用上述解决方案了。它起作用,但是我在预览转换方面遇到了问题,因为流(编码)与UI上的不同,而UI可能会动态更改。现在已经实现了仅有一个预览并在OpenGLRender中具有附加上下文。还需要重新设计一些cpp代码。太复杂了,无法在此处发布 :) - zoki
啊,谢谢你。我希望能有更简单的方法来告诉相机预览表面使用Mediacodec输入表面。我会更加关注使用OpenGLRenderer。我正在做的编码的一部分是在cpp中完成的,但我希望将其与相机分开 - 还需要确定。无论如何,还是非常感谢! - nymeria
1
@nymeria,请查看我在最后一次编辑中添加的链接,以查看新的解决方案。 - zoki
显示剩余2条评论

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