Search code examples
androidbitmapandroid-bitmapglsurfaceviewegl

Why do i always get a whole black picture which screenshoot by GLSurfaceView?


I use the GLSurfaceView as the view to display the camera preview data. I use createBitmapFromGLSurface aim at grabPixels and save it to Bitmap.

However, I always get a whole black picture after save the bitmap to file. Where am i wrong?

Following is my code snippet.

@Override
public void onDrawFrame(GL10 gl) {
    if (mIsNeedCaptureFrame) {
        mIsNeedCaptureFrame = false;
        createBitmapFromGLSurface(width, height);
    }
}

private void createBitmapFromGLSurface(int w, int h) {
    ByteBuffer buf = ByteBuffer.allocateDirect(w * h * 4);
    buf.position(0);
    buf.order(ByteOrder.LITTLE_ENDIAN);
    GLES20.glReadPixels(0, 0, w, h,
            GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, buf);
    buf.rewind();

    Bitmap bmp = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
    bmp.copyPixelsFromBuffer(buf);

    Log.i(TAG, "createBitmapFromGLSurface w:" + w + ",h:" + h);
    mFrameCapturedCallback.onFrameCaptured(bmp);
}

Update:

public void captureFrame(FrameCapturedCallback callback) {
    mIsNeedCaptureFrame = true;
    mCallback = callback;
}

public void takeScreenshot() {
    final int width = mIncomingWidth;
    final int height = mIncomingHeight;
    EglCore eglCore = new EglCore(EGL14.eglGetCurrentContext(), EglCore.FLAG_RECORDABLE);
    OffscreenSurface surface = new OffscreenSurface(eglCore, width, height);
    surface.makeCurrent();
    Bitmap bitmap = surface.captureFrame();

    for (int x = 0, y = 0; x < 100; x++, y++) {
        Log.i(TAG, "getPixel:" + bitmap.getPixel(x, y));
    }
    surface.release();
    eglCore.release();
    mCallback.onFrameCaptured(bitmap);
}

@Override
public void onDrawFrame(GL10 gl) {

    mSurfaceTexture.updateTexImage();

    if (mIsNeedCaptureFrame) {
        mIsNeedCaptureFrame = false;
        takeScreenshot();
        return;
    }
    ....
 }

The logs are following:

getPixel:0
getPixel:0
getPixel:0
getPixel:0
getPixel:0
getPixel:0
getPixel:0
getPixel:0
getPixel:0
getPixel:0
getPixel:0
...

Solution

  • This won't work.

    To understand why, bear in mind that a SurfaceView Surface is a queue of buffers with a producer-consumer relationship. When displaying camera preview data, the Camera is the producer, and the system compositor (SurfaceFlinger) is the consumer. Only the producer can send data to the Surface -- there can be only one producer at a time -- and only the consumer can examine buffers from the Surface.

    If you were drawing on the Surface yourself, so your app would be the producer, you would be able to see what you've drawn, but only while inonDrawFrame(). When it returns, GLSurfaceView calls eglSwapBuffers(), and the frame you've drawn is sent off to the consumer. (Technically, because the buffers are in a pool and are re-used, you can read frames outside onDrawFrame(); but what you're reading would be stale data from 1-2 frames back, not the one you just drew.)

    What you're doing here is reading data from an EGLSurface that has never been drawn on and isn't connected to the SurfaceView. That's why it's always reading black. The Camera doesn't "draw" the preview, it just takes a buffer of YUV data and shoves it into the BufferQueue.

    If you want to show the preview and capture frames using GLSurfaceView, see the "show + capture camera" example in Grafika. You can replace the MediaCodec code with a glReadPixels() (see EglSurfaceBase.saveFrame(), which looks very much like what you have).