MediaCodec с поверхностным входом: запись в фоновом режиме

Я работаю над приложением для кодирования видео, которое я хочу предотвратить, если действие Хостинг входит в фоновый режим, или экран выключается/включается.

Архитектура моего кодировщика основана на отличном примере CameraToMpegTest с добавлением отображения кадров камеры в GLSurfaceView (см. Github ссылки ниже). В настоящее время я выполняю фоновое замещение с помощью решения с двумя состояниями:

  • Когда объект хостинга находится на переднем плане, закодируйте один видеокадр при каждом вызове GLSurfaceView.Renderer onDrawFrame. Это позволяет мне получить доступ к состоянию EGL GLSurfaceView в пакетах, чтобы не блокировать другие события, помещенные в очередь на поток рендеринга.

  • Когда хостинг-активность входит в фоновый режим, остановите кодировку onDrawFrame и закодируйте кадры на другом фоновом потоке в цикле. Этот режим идентичен примеру CameraToMpegTest.

Однако, если экран выключен, GLCurfaceView EGLContext теряется и появляется новый вызов onSurfaceCreated. В этом случае мы должны воссоздать поверхность окна EGL, подключенную к поверхности ввода MediaCodec. К сожалению, этот 2-й вызов eglCreateWindowSurface дает:

E/libEGL(18839): EGLNativeWindowType 0x7a931098 already connected to another API

До вызова я освободит все ресурсы EGL, подключенные к поверхности Android.

Есть ли способ поменять EGSL-поверхность, связанную с поверхностью ввода MediaCodec?

Полный источник моего тестового приложения находится на Github. Основная деятельность.

Обновление. Я применил полученные здесь уроки в виде видео sdk для Android на основе классов MediaCodec и MediaMuxer. Надеюсь, это поможет!

Ответ 1

Фон сначала...

Когда вы вызываете eglCreateWindowSurface(), обертка EGL для Android деструктор поверхности вызывает native_window_api_disconnect(), чтобы отключить поверхность EGL от BufferQueue. Поверхность EGL подсчитывается по ссылке, при этом refcount увеличивается, когда поверхность передается на eglMakeCurrent(), поэтому для уничтожения должны произойти две вещи:

  • eglDestroySurface() следует называть
  • поверхность EGL не должна быть "текущей" в любом потоке.

Второй элемент требует вызова eglMakeCurrent() с другой поверхностью EGL (или EGL_NO_SURFACE) или вызовом eglReleaseThread() для любого потока, который ранее использовал поверхность. Один из быстрых способов подтвердить, что это делается, - добавить регистрацию до вызовов на eglMakeCurrent(), когда поверхность сделана текущей и неточной, и сравните идентификаторы потоков, просмотрев вывод logcat с помощью adb logcat -v threadtime. Также может быть полезно использовать запросы EGL, такие как eglGetCurrentSurface(EGL_DRAW), чтобы подтвердить, что вы выполняете ток в потоке, который сделал поверхность текущей.

Если поверхность EGL не разрушена, она не будет отключена от Surface, и попытки подключения нового производителя (путем вызова eglCreateWindowSurface с новой поверхностью EGL) будут отклонены с помощью "уже подключенного".

Обновление: Моя реализация теперь доступна в тестовом проекте Grafika. Если вы установите это, выберите "Показать + захватить камеру", начните запись, переключите питание, а затем прекратите запись, у вас должен быть полный фильм с длинной паузой посередине. Вы можете отступить, выбрать "Воспроизвести видео" и выбрать "camera-test.mp4", чтобы просмотреть его.