当使用来自 MediaCodec 的 Surface 时,eglSwapBuffers 失败并显示 EGL_BAD_SURFACE
eglSwapBuffers fails with EGL_BAD_SURFACE when using a Surface from MediaCodec
我正在尝试使用 MediaCodec 和 Surfaces 对电影进行编码(像素缓冲模式有效,但性能不够好)。但是,每次我尝试调用 eglSwapBuffers()
时,它都会以 EGL_BAD_SURFACE
失败,因此,dequeueOutputBuffer()
总是 returns -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()
。有了surface之后,我把它变成一个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;
}
当新帧从相机到达时,我将其发送到屏幕和另一个处理记录的 class。 class 只是将其渲染到从 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
,我还没有找到进一步调试的方法。
更新
我试过在使它成为当前表面的调用之后查询当前表面,它总是 returns 一个格式错误的表面(往里面看,我可以看到句柄是 0
并且在查询时它总是失败)。看起来 eglMakeCurrent()
默默地未能将表面设置为上下文。
此外,我确定这个问题出现在高通芯片(Adreno)上,而不是麒麟上,所以它肯定与原生 OpenGL 实现有关(这在某种程度上很有趣,因为我一直注意到 Adreno 在它时更宽容涉及 "bad" OpenGL 配置)
已修复!事实证明 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,但我很高兴仍然有一个可行的解决方案。
我正在尝试使用 MediaCodec 和 Surfaces 对电影进行编码(像素缓冲模式有效,但性能不够好)。但是,每次我尝试调用 eglSwapBuffers()
时,它都会以 EGL_BAD_SURFACE
失败,因此,dequeueOutputBuffer()
总是 returns -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()
。有了surface之后,我把它变成一个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;
}
当新帧从相机到达时,我将其发送到屏幕和另一个处理记录的 class。 class 只是将其渲染到从 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
,我还没有找到进一步调试的方法。
更新
我试过在使它成为当前表面的调用之后查询当前表面,它总是 returns 一个格式错误的表面(往里面看,我可以看到句柄是 0
并且在查询时它总是失败)。看起来 eglMakeCurrent()
默默地未能将表面设置为上下文。
此外,我确定这个问题出现在高通芯片(Adreno)上,而不是麒麟上,所以它肯定与原生 OpenGL 实现有关(这在某种程度上很有趣,因为我一直注意到 Adreno 在它时更宽容涉及 "bad" OpenGL 配置)
已修复!事实证明 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,但我很高兴仍然有一个可行的解决方案。