使用MediaCodec中的Surface时,eglSwapBuffers失败,并显示EGL_BAD_SURFACE

时间:2019-03-26 17:49:26

标签: android opengl-es mediacodec video-encoding egl

我正在尝试使用MediaCodec和Surfaces对电影进行编码(像素缓冲模式有效,但性能不够好)。但是,每次我尝试调用eglSwapBuffers()时,都会失败并显示EGL_BAD_SURFACE,因此dequeueOutputBuffer()总是返回-1(INFO_TRY_AGAIN_LATER

我已经看到了Bigflake和Grafika上的示例,并且我有另一个可以正常工作的项目,但是我需要在另一个稍有不同的设置中使它工作。

我目前有一个GLSurfaceView,它可以进行屏幕渲染,并带有自定义EGLContextFactory / EGLConfigChooser。这使我可以创建共享上下文,以用于本机库中的单独OpenGL渲染。它们是使用EGL10创建的,但这不应该成为问题,因为据我所知,底层上下文仅关心客户端版本。

我已使用以下配置确保上下文可记录:

private android.opengl.EGLConfig chooseConfig14(android.opengl.EGLDisplay display) {
        // Configure EGL for recording and OpenGL ES 3.x
        int[] attribList = {
                EGL14.EGL_RED_SIZE, 8,
                EGL14.EGL_GREEN_SIZE, 8,
                EGL14.EGL_BLUE_SIZE, 8,
                EGL14.EGL_ALPHA_SIZE, 8,
                EGL14.EGL_RENDERABLE_TYPE, EGLExt.EGL_OPENGL_ES3_BIT_KHR,
                EGLExt.EGL_RECORDABLE_ANDROID, 1,
                EGL14.EGL_NONE
        };

        android.opengl.EGLConfig[] configs = new android.opengl.EGLConfig[1];
        int[] numConfigs = new int[1];
        if (!EGL14.eglChooseConfig(display, attribList, 0, configs, 0,
                configs.length, numConfigs, 0)) {
            return null;
        }

        return configs[0];
    }

现在,我试图简化场景,因此在开始记录时,我将MediaCodec的实例初始化为编码器,并在GLSurfaceView的线程上调用createInputSurface()。有了曲面后,将其转换为EGLSurface(EGL14),如下所示:

EGLSurface createEGLSurface(Surface surface) {
        if (surface == null) return null;

        int[] surfaceAttribs = { EGL14.EGL_NONE };

        android.opengl.EGLDisplay display = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);

        android.opengl.EGLConfig config = chooseConfig14(display);

        EGLSurface eglSurface = EGL14.eglCreateWindowSurface(display, config, surface, surfaceAttribs, 0);

        return eglSurface;
    }

从相机收到新帧时,我将其发送到屏幕和另一个处理记录的类。该类仅将其呈现到从MediaCodec的输入表面构建的EGLSurface中,如下所示:

public void drawToSurface(EGLSurface targetSurface, int width, int height, long timestampNano, boolean ignoreOrientation) {
        if (mTextures[0] == null) {
            Log.w(TAG, "Attempting to draw without a source texture");
            return;
        }

        EGLContext currentContext = EGL14.eglGetCurrentContext();
        EGLDisplay currentDisplay = EGL14.eglGetCurrentDisplay();

        EGL14.eglMakeCurrent(currentDisplay, targetSurface, targetSurface, currentContext);
        int error = EGL14.eglGetError();

        ShaderProgram program = getProgramForTextureType(mTextures[0].getTextureType());

        program.draw(width, height, TextureRendererView.LayoutType.LINEAR_HORIZONTAL, 0, 1, mTextures[0]);
        error = EGL14.eglGetError();

        EGLExt.eglPresentationTimeANDROID(currentDisplay, targetSurface, timestampNano);
        error = EGL14.eglGetError();

        EGL14.eglSwapBuffers(currentDisplay, targetSurface);
        error = EGL14.eglGetError();

        Log.d(TAG, "drawToSurface");
    }

由于某种原因,eglSwapBuffers()失败并报告EGL_BAD_SURFACE,但我还没有找到进一步调试方法。

更新 我尝试在使当前表面成为当前调用之后查询当前表面,并且该表面总是返回格式错误的表面(从内部看,我可以看到句柄为0,并且在查询时始终失败)。似乎eglMakeCurrent()默默地未能将绑定表面设置为上下文。

此外,我确定此问题出现在高通公司的芯片(Adreno)上,而不是麒麟上,所以它肯定与本机OpenGL实现有关(这很有趣,因为我一直注意到Adreno在处理问题时更加宽容“错误的” OpenGL配置)

1 个答案:

答案 0 :(得分:0)

已修复!事实证明,EGL10和EGL14看起来可以很好地配合使用,但是在某些情况下会以非常微妙的方式失败,例如我遇到的那种方式。

就我而言,我编写的EGLContextFactory是使用EGL10创建基本的OpenGL ES上下文,然后根据需要再次使用EGL10创建更多的共享上下文。虽然我可以使用EGL14(在Java或C语言中)检索它们,并且上下文句柄始终是正确的(在上下文之间共享纹理就像一个咒语一样工作),但是当尝试使用从上下文或EGL10起源创建的EGLSurface时,它神秘地失败了...在Adreno芯片上。

解决方案是将EGLContextFactory切换为从使用EGL14创建的上下文开始,然后继续使用EGL14创建共享上下文。对于仍然需要EGL10的GLSurfaceView,我不得不使用一个hack

    @Override
    public javax.microedition.khronos.egl.EGLContext createContext(EGL10 egl10, javax.microedition.khronos.egl.EGLDisplay eglDisplay, javax.microedition.khronos.egl.EGLConfig eglConfig) {
        EGLContext context = createContext();
        boolean success = EGL14.eglMakeCurrent(mBaseEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, context);

        if (!success) {
            int error = EGL14.eglGetError();
            Log.w(TAG, "Failed to create a context. Error: " + error);
        }

        javax.microedition.khronos.egl.EGLContext egl14Context = egl10.eglGetCurrentContext(); //get an EGL10 context representation of our EGL14 context
        javax.microedition.khronos.egl.EGLContext trueEGL10Context = egl10.eglCreateContext(eglDisplay, eglConfig, egl14Context, glAttributeList);

        destroyContext(context);
        return trueEGL10Context;
    }

这是用EGL14创建一个新的共享上下文,使其成为最新的,然后检索其EGL10版本。该版本不能直接使用(由于我无法完全理解的原因),但是该版本的另一个共享上下文也可以正常使用。然后可以破坏开始的EGL14上下文(在我的情况下,将其放入堆栈中,稍后再使用)。

我真的很想知道为什么需要这种hack,但我很高兴能有一个可行的解决方案。