// 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();
}
}
}
}