Android Camera2: самый оптимальный и быстрый способ изменить выходную поверхность, установленную на лету - PullRequest
3 голосов
/ 07 марта 2019

Я создаю приложение для потоковой передачи видео, которое адаптирует битрейт видео к доступной полосе пропускания восходящей линии связи, и мне бы хотелось, чтобы оно динамически изменяло разрешение видео, чтобы на меньших битрейтах не было так много артефактов сжатия. Хотя у меня все работает, выпуская MediaCodec и вызывая abortCaptures() и stopRepeating() на CameraCaptureSession, а затем настраивая все для нового разрешения, это вызывает очень заметное прерывание в потоке - по крайней мере, половину второй в моих тестах.

Я использую OpenGL для масштабирования изображения, когда камера изначально не поддерживает требуемое разрешение, аналогично this . Я инициализирую сеанс захвата с двумя поверхностями - одной для предварительного просмотра для пользователя (используя TextureView) и одной для кодера, это либо непосредственно поверхность ввода MediaCodec, либо моя поверхность текстуры OpenGL.

Это потенциально может быть решено с помощью MediaCodec.createPersistentInputSurface(), так как я смогу повторно использовать этот экземпляр масштабирующего устройства при изменении разрешения, и мне не придется ничего делать с сеансом захвата, поскольку никаких поверхностных изменений не происходит, пока Это касается камеры, но она доступна только с API 23, и мне нужна эта реализация для поддержки API 21.

Кроме того, существует проблема с повреждением и повторным созданием поверхностей. Например, когда пользователь нажимает кнопку «Назад», действие и TextureView, которое оно содержит, уничтожаются, что делает поверхность предварительного просмотра недействительной. Затем, когда пользователь снова переходит к этому действию, создается новый TextureView, и мне нужно начать показывать предварительный просмотр в нем, не внося никакого отставания в поток, видимый масштабирующим устройством / кодировщиком.

Итак, мой вопрос в целом: как изменить набор выходных поверхностей в CameraCaptureSession или воссоздать CameraCaptureSession, в то же время внося как можно меньшую задержку в видеопоток?

1 Ответ

4 голосов
/ 07 марта 2019

Как выясняется, контекст OpenGL, который на самом деле содержит текстуры, в том числе те, которым камера обеспечивает кадры, не связан с каким-либо конкретным местом назначения вывода.Таким образом, я смог заставить мой видеоскейлер изменить свою выходную поверхность после инициализации, например так:

...
}else if(inputMessage.what==MSG_CHANGE_SURFACE){
    // Detach the current thread from the context, as a precaution
    EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT);
    checkEglError("eglMakeCurrent 1");

    // Destroy the old EGL surface and release its Android counterpart
    // the old surface belongs to the previous, now released, MediaCodec instance
    EGL14.eglDestroySurface(mEGLDisplay, mEGLSurface);
    checkEglError("eglDestroySurface");
    surface.release(); // surface is a field that holds the current MediaCodec encoder input surface

    surface=(Surface) inputMessage.obj;
    dstW=inputMessage.arg1; // these are used in glViewport and the fragment shader
    dstH=inputMessage.arg2;

    // Create a new EGL surface for the new MediaCodec instance
    int[] surfaceAttribs={
        EGL14.EGL_NONE
    };
    mEGLSurface=EGL14.eglCreateWindowSurface(mEGLDisplay, configs[0], surface, surfaceAttribs, 0);
    checkEglError("eglCreateWindowSurface");

    // Make it current for the current thread
    EGL14.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext);
    checkEglError("eglMakeCurrent 2");

    // That's it, any subsequent draw calls will render to the new surface
}

При таком подходе не требуется повторная инициализация CameraCaptureSession, так как нет изменений внабор поверхностей, на которые выводит камера.

...