#android #android-mediacodec #android-mediarecorder
#Android #android-mediacodec #android-mediarecorder
Вопрос:
Я хочу использовать MediaRecorder
для записи видео вместо MediaCodec
, потому что это очень просто в использовании, как мы знаем.
Я также хочу использовать OpenGL для обработки кадров во время записи
Затем я использую пример кода из образца ContinuousCaptureActivity от Grafika, чтобы инициализировать контекст рендеринга EGL, создать cameraTexture
и передать его в Camera2 API как Surface
https://github.com/google/grafika/blob/master/app/src/main/java/com/android/grafika/ContinuousCaptureActivity.java#L392
и создайте EGLSurface encodeSurface
из нашего recorderSurface
https://github.com/google/grafika/blob/master/app/src/main/java/com/android/grafika/ContinuousCaptureActivity.java#L418
и так далее (обработка кадров, как в примере Grafika, все то же самое, что и в примере кода Grafika code)
Затем, когда я начинаю запись ( MediaRecorder.start()
), видео записывается нормально, если источник звука не был установлен
Но если запись звука также включена
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC)
...
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
Тогда итоговое видео имеет большую длительность, и его на самом деле невозможно воспроизвести. Таким образом, аудиокодер MediaRecorder все портит при использовании Surface в качестве ввода и GLES для добавления и обработки кадров
Я понятия не имею, как это исправить.
Вот мой код для обработки кадров (основанный на примере 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
}
}
Комментарии:
1. p.s. с источником звука все записывается нормально на эмуляторе, но проблема с реальным устройством (Samsung S8)
Ответ №1:
Очень вероятно, что ваши временные метки не находятся в одной временной базе. Система записи мультимедиа обычно требует временных меток в базе данных uptimeMillis, но многие устройства с камерами выдают данные в базе данных прошедшего реального времени. Один считает время, когда устройство находится в режиме глубокого сна, а другой — нет; чем дольше прошло с момента перезагрузки устройства, тем больше становится расхождение.
Это не будет иметь значения, пока вы не добавите звук, поскольку временные метки внутреннего аудио MediaRecorder будут в миллиметрах времени ожидания, в то время как временные метки кадра камеры будут отображаться как истекшее реальное время. Расхождение более чем на несколько долей секунды, вероятно, будет заметно как плохая синхронизация A / V; несколько минут или больше просто все испортят.
Когда камера напрямую взаимодействует со стеком записи мультимедиа, она автоматически корректирует временные метки; поскольку вы разместили графический процессор посередине, этого не происходит (поскольку камера не знает, куда в конечном итоге попадают ваши кадры).
Вы можете проверить, использует ли камера прошедшее реальное время в качестве временной базы через SENSOR_INFO_TIMESTAMP_SOURCE. Но в любом случае у вас есть несколько вариантов:
- Если камера использует TIMESTAMP_SOURCE_REALTIME, измерьте разницу между двумя временными метками в начале записи и соответствующим образом отрегулируйте временные метки, которые вы вводите в setPresentationTime (
delta = elapsedRealtime - uptimeMillis; timestamp = timestamp - delta;
) - Просто используйте
uptimeMillis() * 1000000
в качестве времени для setPresentationTime. Это может привести к слишком большому искажению A / V, но это легко попробовать.
Комментарии:
1. Спасибо за такой подробный ответ! Я проверил, что возвращается эмулятор Android
TIMESTAMP_SOURCE_UNKNOWN
и возвращается реальное устройствоTIMESTAMP_SOURCE_REALTIME
. Я постараюсь исправить это для реальных устройств (для эмулятора все работает нормально) с помощью предложенных вами решений и отвечу, если это помогло.2. Я попробовал второе решение, и оно работает нормально. Но вы говорите, что это все еще может привести к слишком большому искажению A / V, и лучше использовать первое решение? При использовании первого решения я должен определить
delta = elapsedRealtime - uptimeMillis;
доmediaRecorder.start()
или после?3. Я пробовал первое подобное решение
val timestampDelta = SystemClock.elapsedRealtime() - SystemClock.uptimeMillis()
раньшеmediaRecorder.start()
, а затемsetPresentationTime(cameraTexture.timestamp - callback.getTimestampDelta())
, но это не решило проблему. Итак, на данный момент для меня работает только второе решение. Я также подумал, что mb вы имели в виду неcameraTexture.timestamp
ноSystem.currentTimeMillis()
илиInstant.now().epochSecond
, но также не сработало. Или я совершенно не понял первое решение 🙂4. Обратите внимание, что некоторые из этих методов возвращают миллисекунды, а другие используют наносекунды — возможно, вам потребуется умножить / разделить на 1 000 000 в нескольких местах; извините, я не включил коэффициенты преобразования в свой вариант № 1. Вы можете вычислить дельту в любой момент, когда ваше приложение видно — процессор, как правило, не собирается переходить в спящий режим, пока ваше приложение запущено.