Поверхностный ввод MediaRecorder с OpenGL — проблема, если включена запись звука

#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. Но в любом случае у вас есть несколько вариантов:

  1. Если камера использует TIMESTAMP_SOURCE_REALTIME, измерьте разницу между двумя временными метками в начале записи и соответствующим образом отрегулируйте временные метки, которые вы вводите в setPresentationTime ( delta = elapsedRealtime - uptimeMillis; timestamp = timestamp - delta; )
  2. Просто используйте 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. Вы можете вычислить дельту в любой момент, когда ваше приложение видно — процессор, как правило, не собирается переходить в спящий режим, пока ваше приложение запущено.