// started from https://github.com/google/grafika/blob/f3c8c3dee60153f471312e21acac8b3a3cddd7dc/src/com/android/grafika/gles/EglSurfaceBase.java /* * Copyright 2013 Google Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.cine.android.streaming.gles; import android.opengl.EGL14; import android.opengl.EGLSurface; import android.opengl.GLES20; import android.util.Log; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import io.cine.android.streaming.ScreenShot; /** * Common base class for EGL surfaces. * <p/> * There can be multiple surfaces associated with a single context. */ public class EglSurfaceBase { protected static final String TAG = GlUtil.TAG; // EglCore object we're associated with. It may be associated with multiple surfaces. protected EglCore mEglCore; private EGLSurface mEGLSurface = EGL14.EGL_NO_SURFACE; private int mWidth = -1; private int mHeight = -1; protected EglSurfaceBase(EglCore eglCore) { mEglCore = eglCore; } /** * Creates a window surface. * <p/> * * @param surface May be a Surface or SurfaceTexture. */ public void createWindowSurface(Object surface) { if (mEGLSurface != EGL14.EGL_NO_SURFACE) { throw new IllegalStateException("surface already created"); } mEGLSurface = mEglCore.createWindowSurface(surface); // Don't cache width/height here, because the size of the underlying surface can change // out from under us (see e.g. HardwareScalerActivity). //mWidth = mEglCore.querySurface(mEGLSurface, EGL14.EGL_WIDTH); //mHeight = mEglCore.querySurface(mEGLSurface, EGL14.EGL_HEIGHT); } /** * Creates an off-screen surface. */ public void createOffscreenSurface(int width, int height) { if (mEGLSurface != EGL14.EGL_NO_SURFACE) { throw new IllegalStateException("surface already created"); } mEGLSurface = mEglCore.createOffscreenSurface(width, height); mWidth = width; mHeight = height; } /** * Returns the surface's width, in pixels. * <p/> * If this is called on a window surface, and the underlying surface is in the process * of changing size, we may not see the new size right away (e.g. in the "surfaceChanged" * callback). The size should match after the next buffer swap. */ public int getWidth() { if (mWidth < 0) { return mEglCore.querySurface(mEGLSurface, EGL14.EGL_WIDTH); } else { return mWidth; } } /** * Returns the surface's height, in pixels. */ public int getHeight() { if (mHeight < 0) { return mEglCore.querySurface(mEGLSurface, EGL14.EGL_HEIGHT); } else { return mHeight; } } /** * Release the EGL surface. */ public void releaseEglSurface() { mEglCore.releaseSurface(mEGLSurface); mEGLSurface = EGL14.EGL_NO_SURFACE; mWidth = mHeight = -1; } /** * Makes our EGL context and surface current. */ public void makeCurrent() { mEglCore.makeCurrent(mEGLSurface); } /** * Makes our EGL context and surface current for drawing, using the supplied surface * for reading. */ public void makeCurrentReadFrom(EglSurfaceBase readSurface) { mEglCore.makeCurrent(mEGLSurface, readSurface.mEGLSurface); } /** * Calls eglSwapBuffers. Use this to "publish" the current frame. * * @return false on failure */ public boolean swapBuffers() { boolean result = mEglCore.swapBuffers(mEGLSurface); if (!result) { Log.d(TAG, "WARNING: swapBuffers() failed"); } return result; } /** * Sends the presentation time stamp to EGL. * * @param nsecs Timestamp, in nanoseconds. */ public void setPresentationTime(long nsecs) { mEglCore.setPresentationTime(mEGLSurface, nsecs); } /** * Saves the EGL surface to a file. * <p/> * Expects that this object's EGL surface is current. * * This is an amended version of Grafika's SaveFrame method. * * We define the ByteBuffer here (rather than in the screenshot object) * because the bytebuffer creation really relies on the EGL surface * being current and this class is where this is guaranteed. * * Then the entire process of saving the bitmap, including scaling, etc. * happens through the screenshot's saveBitmapFromButter method. * * To ensure that saving doesn't clog the frame buffering for the camera preview * we call it from a runnable in the method. It keeps the app running smoothly * and it's cheap on resources * */ public void saveFrame(ScreenShot screenShot) throws IOException { if (!mEglCore.isCurrent(mEGLSurface)) { throw new RuntimeException("Expected EGL context/surface is not current"); } int width = getWidth(); int height = getHeight(); ByteBuffer buf = ByteBuffer.allocateDirect(width * height * 4); buf.order(ByteOrder.LITTLE_ENDIAN); GLES20.glReadPixels(0, 0, width, height, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, buf); GlUtil.checkGlError("glReadPixels"); buf.rewind(); Runnable saveBitmap = new SaveBitmapRunnable(screenShot, buf, width, height); saveBitmap.run(); } private static class SaveBitmapRunnable implements Runnable{ private final ByteBuffer buf; private final int width; private final int height; private final ScreenShot screenShot; SaveBitmapRunnable(final ScreenShot screenShot, final ByteBuffer buf, final int width, final int height){ this.width = width; this.height = height; this.buf = buf; this.screenShot = screenShot; } @Override public void run() { try { screenShot.saveBitmapFromBuffer(buf, width, height); } catch (IOException e) { e.printStackTrace(); } } } }