带 OpenGL 的 MediaRecorder Surface Input - 如果启用录音会出现问题
MediaRecorder Surface Input with OpenGL - issue if audio recording is enabled
我想使用 MediaRecorder
而不是 MediaCodec
来录制视频,因为我们知道它非常容易使用。
我也想在录制时使用OpenGL处理帧
然后我使用 Grafika 的 ContinuousCaptureActivity 示例中的示例代码来初始化 EGL 渲染上下文,创建 cameraTexture
并将其作为 Surface
https://github.com/google/grafika/blob/master/app/src/main/java/com/android/grafika/ContinuousCaptureActivity.java#L392[=21 传递给 Camera2 API =]
并从我们的 recorderSurface
https://github.com/google/grafika/blob/master/app/src/main/java/com/android/grafika/ContinuousCaptureActivity.java#L418
创建 EGLSurface encodeSurface
等等(处理帧与 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
}
}
很可能您的时间戳不在同一时基中。媒体记录系统通常需要 uptimeMillis timebase, but many camera devices produce data in the elapsedRealtime 时基中的时间戳。一个在设备处于深度睡眠状态时计算时间,而另一个则不;重启设备的时间越长,差异就越大。
在添加音频之前这并不重要,因为 MediaRecorder 的内部音频时间戳将以 uptimeMillis 为单位,而相机帧时间戳将以 elapsedRealtime 的形式出现。超过几分之一秒的差异可能会被视为错误的 A/V 同步;几分钟或更长时间只会把一切都搞砸。
当相机直接与媒体记录堆栈对话时,它会自动调整时间戳;因为您将 GPU 放在中间,所以不会发生这种情况(因为相机不知道那是您的帧最终要去的地方)。
您可以通过SENSOR_INFO_TIMESTAMP_SOURCE检查相机是否使用elapsedRealtime作为时基。但无论如何,你有几个选择:
- 如果相机使用TIMESTAMP_SOURCE_REALTIME,在开始记录时测量两个时间戳之间的差异,并相应地调整您输入setPresentationTime的时间戳(
delta = elapsedRealtime - uptimeMillis; timestamp = timestamp - delta;
)
- 只需使用
uptimeMillis() * 1000000
作为setPresentationTime的时间即可。这可能会导致 A/V 偏差太大,但很容易尝试。
我想使用 MediaRecorder
而不是 MediaCodec
来录制视频,因为我们知道它非常容易使用。
我也想在录制时使用OpenGL处理帧
然后我使用 Grafika 的 ContinuousCaptureActivity 示例中的示例代码来初始化 EGL 渲染上下文,创建 cameraTexture
并将其作为 Surface
https://github.com/google/grafika/blob/master/app/src/main/java/com/android/grafika/ContinuousCaptureActivity.java#L392[=21 传递给 Camera2 API =]
并从我们的 recorderSurface
https://github.com/google/grafika/blob/master/app/src/main/java/com/android/grafika/ContinuousCaptureActivity.java#L418
encodeSurface
等等(处理帧与 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
}
}
很可能您的时间戳不在同一时基中。媒体记录系统通常需要 uptimeMillis timebase, but many camera devices produce data in the elapsedRealtime 时基中的时间戳。一个在设备处于深度睡眠状态时计算时间,而另一个则不;重启设备的时间越长,差异就越大。
在添加音频之前这并不重要,因为 MediaRecorder 的内部音频时间戳将以 uptimeMillis 为单位,而相机帧时间戳将以 elapsedRealtime 的形式出现。超过几分之一秒的差异可能会被视为错误的 A/V 同步;几分钟或更长时间只会把一切都搞砸。
当相机直接与媒体记录堆栈对话时,它会自动调整时间戳;因为您将 GPU 放在中间,所以不会发生这种情况(因为相机不知道那是您的帧最终要去的地方)。
您可以通过SENSOR_INFO_TIMESTAMP_SOURCE检查相机是否使用elapsedRealtime作为时基。但无论如何,你有几个选择:
- 如果相机使用TIMESTAMP_SOURCE_REALTIME,在开始记录时测量两个时间戳之间的差异,并相应地调整您输入setPresentationTime的时间戳(
delta = elapsedRealtime - uptimeMillis; timestamp = timestamp - delta;
) - 只需使用
uptimeMillis() * 1000000
作为setPresentationTime的时间即可。这可能会导致 A/V 偏差太大,但很容易尝试。