使用OpenGL的MediaRecorder Surface输入 - 如果启用音频录制会出现问题

3
我想使用MediaRecorder来录制视频,而不是使用MediaCodec,因为我们知道MediaRecorder非常易于使用。
我还想在录制时使用OpenGL处理帧。
之后,我使用Grafika的ContinuousCaptureActivity示例代码初始化EGL渲染上下文,创建cameraTexture并将其作为Surface传递给Camera2 API,如下链接代码https://github.com/google/grafika/blob/master/app/src/main/java/com/android/grafika/ContinuousCaptureActivity.java#L392
然后从我们的recorderSurface创建EGLSurface encodeSurface,如下链接代码https://github.com/google/grafika/blob/master/app/src/main/java/com/android/grafika/ContinuousCaptureActivity.java#L418
接下来(像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
    }
}

PS:使用音频源时,模拟器上录制正常,但在真实设备(三星S8)上存在问题。 - user924
1个回答

4
很可能你的时间戳(timestamps)不在同一时间基准上。媒体记录系统通常希望时间戳使用uptimeMillis 的时间基准,但许多相机设备会产生使用elapsedRealtime 时间基准的数据。其中一个计时器会在设备深度睡眠期间持续计算时间,而另一个则不会;距离设备重启时间越长,它们之间的差异就越大。
在添加音频之前这并不重要,因为MediaRecorder内部音频的时间戳将是uptimeMillis,而相机帧的时间戳将以elapsedRealtime形式出现。秒数的偏差超过几十分之一秒可能会明显影响音视频同步质量;几分钟或更长时间将导致一切变得混乱。
当相机直接与媒体记录堆栈通信时,它会自动调整时间戳;由于您将GPU置于中间,所以这不会发生(因为相机不知道帧最终去了哪里)。
您可以通过 SENSOR_INFO_TIMESTAMP_SOURCE 检查相机是否使用elapsedRealtime作为时间基准。无论如何,您有几种选择:
  1. 如果摄像机使用TIMESTAMP_SOURCE_REALTIME,则在录制开始时测量两个时间戳的差异,并相应地调整您输入到setPresentationTime中的时间戳( delta = elapsedRealtime - uptimeMillis; timestamp = timestamp - delta;
  2. 只需使用 uptimeMillis() * 1000000 作为setPresentationTime的时间。这可能导致太多的音视频偏差,但是很容易尝试。

感谢您提供如此详细的答案!我已经检查了Android模拟器返回的是“TIMESTAMP_SOURCE_UNKNOWN”,而真实设备返回的是“TIMESTAMP_SOURCE_REALTIME”。我将尝试使用您建议的解决方案来修复真实设备上的问题(对于模拟器,它可以正常工作),如果有帮助,我会回复您的。 - user924
我尝试了第二种解决方案,它工作得很好。但你说它仍然可能导致A / V过度偏移,最好使用第一种解决方案?如果使用第一种解决方案,我应该在mediaRecorder.start()之前还是之后定义delta = elapsedRealtime - uptimeMillis; - user924
我尝试了第一种解决方案,像这样 val timestampDelta = SystemClock.elapsedRealtime() - SystemClock.uptimeMillis()mediaRecorder.start() 之前,然后 setPresentationTime(cameraTexture.timestamp - callback.getTimestampDelta()),但它没有解决问题。所以现在只有第二种解决方案对我有效。我还想到你可能不是指 cameraTexture.timestamp 而是 System.currentTimeMillis()Instant.now().epochSecond,但也没有起作用。或者我完全没有理解第一种解决方案 :) - user924
请注意,其中一些方法返回毫秒,而其他方法使用纳秒 - 在某些地方您可能需要乘以/除以1,000,000; 抱歉我在选项#1中没有包含转换因子。您可以在应用程序可见的任何时候计算增量 - CPU通常不会在运行应用程序时进入睡眠状态。 - Eddy Talvala

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