首页 > 解决方案 > MediaRecorder Surface Input with OpenGL - 如果启用了音频录制,则会出现问题

问题描述

我想MediaRecorder用来录制视频而不是MediaCodec,因为我们知道它很容易使用。

我还想在录制时使用 OpenGL 处理帧

然后我使用 Grafika 的 ContinuousCaptureActivity 示例中的示例代码来初始化 EGL 渲染上下文,创建cameraTexture并将其传递给 Camera2 API,如Surface https://github.com/google/grafika/blob/master/app/src/main/java/com/android /grafika/ContinuousCaptureActivity.java#L392

encodeSurface并从我们的https://github.com/google/grafika/blob/master/app/src/main/java/com/android/grafika/ContinuousCaptureActivity.java#L418创建 EGLSurfacerecorderSurface

依此类推(在 Grafika 示例中处理帧,一切都与示例代码中的 Grafika 代码相同)

然后当我开始录制(MediaRecorder.start())时,如果未设置音频源,它会录制视频

但是如果也启用了录音

mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC)
...
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC)

然后最终视频的持续时间(长度)很长,而且它并不是真正可播放的。因此,当使用 Surface 作为输入和使用 GLES 添加和处理帧时,MediaRecorder 音频编码器会破坏一切

我不知道如何解决它。

这是我处理帧的代码(基于 Grafika 示例,几乎相同):

class GLCameraFramesRender(
    private val width: Int,
    private val height: Int,
    private val callback: Callback,
    recorderSurface: Surface,
    eglCore: EglCore
) : OnFrameAvailableListener {
    private val fullFrameBlit: FullFrameRect
    private val textureId: Int
    private val encoderSurface: WindowSurface
    private val tmpMatrix = FloatArray(16)
    private val cameraTexture: SurfaceTexture
    val cameraSurface: Surface

    init {
        encoderSurface = WindowSurface(eglCore, recorderSurface, true)
        encoderSurface.makeCurrent()

        fullFrameBlit = FullFrameRect(Texture2dProgram(Texture2dProgram.ProgramType.TEXTURE_EXT))

        textureId = fullFrameBlit.createTextureObject()

        cameraTexture = SurfaceTexture(textureId)
        cameraSurface = Surface(cameraTexture)
        cameraTexture.setOnFrameAvailableListener(this)
    }

    fun release() {
        cameraTexture.setOnFrameAvailableListener(null)
        cameraTexture.release()
        cameraSurface.release()
        fullFrameBlit.release(false)
        eglCore.release()
    }

    override fun onFrameAvailable(surfaceTexture: SurfaceTexture) {
        if (callback.isRecording()) {
            drawFrame()
        } else {
            cameraTexture.updateTexImage()
        }
    }

    private fun drawFrame() {
        cameraTexture.updateTexImage()

        cameraTexture.getTransformMatrix(tmpMatrix)


        GLES20.glViewport(0, 0, width, height)

        fullFrameBlit.drawFrame(textureId, tmpMatrix)

        encoderSurface.setPresentationTime(cameraTexture.timestamp)

        encoderSurface.swapBuffers()
       
    }

    interface Callback {
        fun isRecording(): Boolean
    }
}

标签: androidandroid-mediacodecandroid-mediarecorder

解决方案


您的时间戳很可能不在同一个时基中。媒体记录系统通常需要 uptimeMillis 时基中的时间戳但许多相机设备会在elapsedRealtime时基中生成数据。一个计算设备处于深度睡眠的时间,另一个不计算;自您重新启动设备以来的时间越长,差异就越大。

除非您添加音频,否则这无关紧要,因为 MediaRecorder 的内部音频时间戳将以 uptimeMillis 形式出现,而相机帧时间戳将以 elapsedRealtime 形式出现。超过几分之一秒的差异可能会被视为糟糕的 A/V 同步;几分钟或更长时间只会把一切搞砸。

当摄像机直接与媒体记录堆栈对话时,它会自动调整时间戳;由于您已将 GPU 放在中间,因此不会发生这种情况(因为相机不知道您的帧最终会去哪里)。

您可以通过SENSOR_INFO_TIMESTAMP_SOURCE检查相机是否使用 elapsedRealtime 作为时基。但无论如何,你有几个选择:

  1. 如果相机使用 TIMESTAMP_SOURCE_REALTIME,在开始录制时测量两个时间戳之间的差异,并相应地调整您输入 setPresentationTime 的时间戳(delta = elapsedRealtime - uptimeMillis; timestamp = timestamp - delta;
  2. 只需uptimeMillis() * 1000000用作 setPresentationTime 的时间。这可能会导致过多的 A/V 偏差,但很容易尝试。

推荐阅读