【问题标题】:glReadPixels does not work in WebRTC Android SurfaceViewRendererglReadPixels 在 WebRTC Android SurfaceViewRenderer 中不起作用
【发布时间】:2016-03-03 05:49:34
【问题描述】:

我正在尝试从 WebRTC Android 应用程序中保存帧。在SurfaceViewRenderer.java 中,它使用 GLES 着色器绘制 YUV 帧。为了保存绘制的框架,我将 grafika 中的 saveFrame() 添加到 SurfaceViewRenderer.java 但是当我调用 saveFrame() 时它不起作用(位图为空)。我以为 glReadPixels() 从当前颜色缓冲区读取像素,但似乎在当前 EGLcontext 中没有调用它?如何调用 glReadPixels() 保存当前帧?

我写的代码是这样的:

在 MainActivity 我添加了这样的按钮。

button.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            bitmap = localSurfaceViewRender.saveFrame();
            if (bitmap != null) {
                Toast.makeText(getApplicationContext(), "Saved!!", Toast.LENGTH_SHORT).show();
                imageView.setImageBitmap(bitmap);
            }
        }
    });

在 SurfaceViewRenderer.java 中,我添加了一些方法来从当前绑定的 egl 中获取值

public int getSurfaceHeight() {
    if (mHeight < 0) {
        return eglBase.surfaceHeight();
    } else {
        return mHeight;
    }
}

public int getSurfaceWidth() {
    if (mWidth < 0) {
        return eglBase.surfaceWidth();
    } else {
        return mWidth;
    }
}


public Bitmap saveFrame() {
    int width = eglBase.surfaceWidth();
    int height = eglBase.surfaceHeight();
    ByteBuffer buf = ByteBuffer.allocateDirect(width * height * 4);
    buf.order(ByteOrder.LITTLE_ENDIAN);
    GLES20.glFinish();
    GLES20.glReadPixels(0, 0, width, height, GLES20.GL_RGB, GLES20.GL_UNSIGNED_BYTE, buf);
    GlUtil.checkNoGLES2Error("glReadPixels");
    buf.rewind();
    Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
    bmp.copyPixelsFromBuffer(buf);
    return bmp;
}

编辑: 这段代码是saveFrame的完整代码

// save as a file
        public void saveFrame(final File file) {
            runOnRenderThread(new Runnable() {
                @Override
                public void run() {
                    int width = eglBase.surfaceWidth();
                    int height = eglBase.surfaceHeight();
                    ByteBuffer buf = ByteBuffer.allocateDirect(width * height * 4);
                    buf.order(ByteOrder.LITTLE_ENDIAN);
                    GLES20.glFinish();
                    GLES20.glReadPixels(0, 0, width, height, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, buf);
                    GlUtil.checkNoGLES2Error("glReadPixels");
                    buf.rewind();

                    // fliping by vertical
                    byte[] tmp = new byte[width * 4];
                    for (int i = 0; i < height / 2; i++) {
                        buf.get(tmp);
                        System.arraycopy(buf.array(), buf.limit() - buf.position(),
                                buf.array(), buf.position() - width * 4, width * 4);
                        System.arraycopy(tmp, 0, buf.array(), buf.limit() - buf.position(), width * 4);
                    }
                    buf.rewind();

                    String filename = file.toString();
                    BufferedOutputStream bos = null;
                    try {
                        bos = new BufferedOutputStream(new FileOutputStream(filename));
                        Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
                        bmp.copyPixelsFromBuffer(buf);
                        bmp.compress(Bitmap.CompressFormat.JPEG, 100, bos);
                        bmp.recycle();
                    } catch (FileNotFoundException e) {
                        e.printStackTrace();
                    } finally {
                        if (bos != null) try {
                            bos.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }

                    Log.d(TAG, "Saved " + width + "x" + height + " frame as '" + filename + "'");
                }
            });
        }

【问题讨论】:

    标签: android opengl-es surfaceview


    【解决方案1】:

    glReadPixels() 函数从当前上下文中读取帧缓冲区的内容。一旦调用eglSwapBuffers(),内容就消失了。

    换句话说,应用程序无法从 SurfaceView 中读取任何内容。因此,您必须在将缓冲区提交给合成器之前读取像素。

    (我实际上不确定如果您在交换缓冲区后立即调用 glReadPixels() 会发生什么......您可能会从回收的缓冲区中读取先前发送的帧,但您不能总是依赖它.)

    您似乎正在从主 UI 线程调用 glReadPixels()。如果 SurfaceView 渲染发生在不同的线程上,则需要从同一个线程访问帧缓冲区,因为那是 EGL 上下文当前所在的位置。给定的上下文一次不能在多个线程中处于当前状态。

    【讨论】:

    • 感谢法登。 SurfaceViewRenderer.java 中有一个runOnRenderThread 方法。我在这个方法中调用了 saveframe() 然后它工作正常。
    • @kju 对我来说在 runOnRenderThread 方法上调用 saveframe() 会返回黑色图像。请帮助你如何做到这一点。谢谢
    • 嘿@kju 如何使这个保存位图异步,这样它就不会使 UI 冻结。我们还需要做什么?我尝试了一些诸如 asynctask 之类的东西,但它似乎不起作用!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-29
    • 1970-01-01
    相关资源
    最近更新 更多