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

Posted

技术标签:

【中文标题】如何在 MediaCodec 编码器和 CameraX 之间共享 Surface【英文标题】:How can I share a Surface between MediaCodec encoder and CameraX 【发布时间】:2020-09-08 02:12:49 【问题描述】:

我想从CameraX(预览用例)获取图像,并使用MediaCodec 将它们编码为h.264 视频。我怎样才能做到这一点? 我正在尝试的是,通过使用Preview.setSurfaceProvider()Preview.Builder() 中使用从MediaCodec.createInputSurface() 返回的Surface。我从Preview.SurfaceProvider 继承了一个类,然后在该设置中配置我的编码器并覆盖onSurfaceRequested() 以从createInputSurface() 返回Surface。这预计会奏效吗?我真的可以像这样共享一个 Surface 并期望 CameraX 写入这个 Surface 并为我的编码器填充输入吗?

是否有更有效的方法来编码实时 CameraX 提要?

注意:我正在使用 KOTLIN

【问题讨论】:

【参考方案1】:

我终于用 CameraX OpenGL 测试中的OpenGLRenderer 解决了这个问题。这适用于 CameraX 的 beta 7 版本。

像往常一样设置 camerax,但使用 2 个预览:

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 google group can be found here对话的一部分出现的较新的解决方案

【讨论】:

谢谢你。连接两者时使用的encoderPreview在哪里?我看到你在glRenderer.attachInputPreview(preview) 中使用了preview。如果你有一个样本,我真的很难让它与我的编码器一起工作,屏幕只是黑色的。 嗨!我犯了一个错误。 glRenderer.attachInputPreview 中应该是encoderPreview。上面已经修复了。 我不再使用上述解决方案。它起作用了,但是我在预览转换时遇到了问题,因为流(编码)与 UI 上的不同,它可以动态更改。现在只需一个预览就实现了所有内容,并且在 OpenGLRender 中有额外的上下文。在 cpp 中也有必要重新编写一些代码。太复杂了,不能在这里发帖:) 啊,谢谢。我希望有一种更简单的方法来告诉相机预览表面使用媒体编解码器输入表面。我将更多地关注使用 OpenGLRenderer。我正在做的部分编码是在 cpp 中,但我希望将它与相机分开 - 我猜要确定。还是谢谢! @nymeria,检查我在上次编辑时添加的链接,以查看新的解决方案。

以上是关于如何在 MediaCodec 编码器和 CameraX 之间共享 Surface的主要内容,如果未能解决你的问题,请参考以下文章

如何设置 MediaCodec 创建的编码器缓冲区大小

如何使用 MediaCodec 将位图编码为视频?

使用 MediaCodec 和 Surface 进行 Android 编码

如何正确使用带有 YUV_420_888 和 MediaCodec 的 ImageReader 将视频编码为 h264 格式?

在 Android 上使用 AudioRecord 和 MediaCodec 编码 AAC 音频

如何将相机预览传递给 MediaCodec.createInputSurface() 创建的 Surface?