// started from: https://github.com/google/grafika/blob/f3c8c3dee60153f471312e21acac8b3a3cddd7dc/src/com/android/grafika/BroadcastActivity.java#L418
package io.cine.android.streaming;
import android.graphics.SurfaceTexture;
import android.opengl.EGL14;
import android.opengl.GLSurfaceView;
import android.util.Log;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import io.cine.android.BroadcastActivity;
import io.cine.android.streaming.gles.FullFrameRect;
import io.cine.android.streaming.gles.Texture2dProgram;
/**
* Renderer object for our GLSurfaceView.
* <p/>
* Do not call any methods here directly from another thread -- use the
* GLSurfaceView#queueEvent() call.
*/
public class CameraSurfaceRenderer implements GLSurfaceView.Renderer {
private static final String TAG = "CameraSurfaceRenderer";
private static final boolean VERBOSE = false;
private static final int RECORDING_OFF = 0;
private static final int RECORDING_ON = 1;
private static final int RECORDING_RESUMED = 2;
private final Muxer mMuxer;
private final float[] mSTMatrix = new float[16];
private BroadcastActivity.CameraHandler mCameraHandler;
private TextureMovieEncoder mVideoEncoder;
private FullFrameRect mFullScreen;
private int mTextureId;
private SurfaceTexture mSurfaceTexture;
private boolean mRecordingEnabled;
private int mRecordingStatus;
private int mFrameCount;
// width/height of the incoming camera preview frames
private boolean mIncomingSizeUpdated;
private int mIncomingWidth;
private int mIncomingHeight;
/**
* Constructs CameraSurfaceRenderer.
* <p/>
*
* @param cameraHandler Handler for communicating with UI thread
* @param movieEncoder video encoder object
* @param muxer output file for encoded video; forwarded to movieEncoder
*/
public CameraSurfaceRenderer(BroadcastActivity.CameraHandler cameraHandler,
TextureMovieEncoder movieEncoder, Muxer muxer) {
mCameraHandler = cameraHandler;
mVideoEncoder = movieEncoder;
// mOutputString = outputString;
mMuxer = muxer;
mTextureId = -1;
mRecordingStatus = -1;
mRecordingEnabled = false;
mFrameCount = -1;
mIncomingSizeUpdated = false;
mIncomingWidth = mIncomingHeight = -1;
}
/**
* Notifies the renderer thread that the activity is pausing.
* <p/>
* For best results, call this *after* disabling Camera preview.
*/
public void notifyPausing() {
if (mSurfaceTexture != null) {
Log.d(TAG, "renderer pausing -- releasing SurfaceTexture");
mSurfaceTexture.release();
mSurfaceTexture = null;
}
if (mFullScreen != null) {
mFullScreen.release(false); // assume the GLSurfaceView EGL context is about
mFullScreen = null; // to be destroyed
}
mIncomingWidth = mIncomingHeight = -1;
}
/**
* Notifies the renderer that we want to stop or start recording.
*/
public void changeRecordingState(boolean isRecording) {
Log.d(TAG, "changeRecordingState: was " + mRecordingEnabled + " now " + isRecording);
mRecordingEnabled = isRecording;
}
/**
* Records the size of the incoming camera preview frames.
* <p/>
* It's not clear whether this is guaranteed to execute before or after onSurfaceCreated(),
* so we assume it could go either way. (Fortunately they both run on the same thread,
* so we at least know that they won't execute concurrently.)
*/
public void setCameraPreviewSize(int width, int height) {
Log.d(TAG, "setCameraPreviewSize");
mIncomingWidth = width;
mIncomingHeight = height;
mIncomingSizeUpdated = true;
}
@Override
public void onSurfaceCreated(GL10 unused, EGLConfig config) {
Log.d(TAG, "onSurfaceCreated");
// We're starting up or coming back. Either way we've got a new EGLContext that will
// need to be shared with the video encoder, so figure out if a recording is already
// in progress.
mRecordingEnabled = mVideoEncoder.isRecording();
if (mRecordingEnabled) {
mRecordingStatus = RECORDING_RESUMED;
} else {
mRecordingStatus = RECORDING_OFF;
}
// Set up the texture blitter that will be used for on-screen display. This
// is *not* applied to the recording, because that uses a separate shader.
mFullScreen = new FullFrameRect(
new Texture2dProgram(Texture2dProgram.ProgramType.TEXTURE_EXT));
mTextureId = mFullScreen.createTextureObject();
// Create a SurfaceTexture, with an external texture, in this EGL context. We don't
// have a Looper in this thread -- GLSurfaceView doesn't create one -- so the frame
// available messages will arrive on the main thread.
mSurfaceTexture = new SurfaceTexture(mTextureId);
// Tell the UI thread to enable the camera preview.
mCameraHandler.sendMessage(mCameraHandler.obtainMessage(
BroadcastActivity.CameraHandler.MSG_SET_SURFACE_TEXTURE, mSurfaceTexture));
}
@Override
public void onSurfaceChanged(GL10 unused, int width, int height) {
Log.d(TAG, "onSurfaceChanged " + width + "x" + height);
mCameraHandler.sendMessage(mCameraHandler.obtainMessage(
BroadcastActivity.CameraHandler.MSG_SURFACE_CHANGED));
}
@Override
public void onDrawFrame(GL10 unused) {
if (VERBOSE) Log.d(TAG, "onDrawFrame tex=" + mTextureId);
// Latch the latest frame. If there isn't anything new, we'll just re-use whatever
// was there before.
mSurfaceTexture.updateTexImage();
// If the recording state is changing, take care of it here. Ideally we wouldn't
// be doing all this in onDrawFrame(), but the EGLContext sharing with GLSurfaceView
// makes it hard to do elsewhere.
if (mRecordingEnabled) {
switch (mRecordingStatus) {
case RECORDING_OFF:
Log.d(TAG, "START recording");
// start recording
mVideoEncoder.startRecording(new TextureMovieEncoder.EncoderConfig(mMuxer, EGL14.eglGetCurrentContext()));
mRecordingStatus = RECORDING_ON;
break;
case RECORDING_RESUMED:
Log.d(TAG, "RESUME recording");
mVideoEncoder.updateSharedContext(EGL14.eglGetCurrentContext());
mRecordingStatus = RECORDING_ON;
break;
case RECORDING_ON:
// yay
break;
default:
throw new RuntimeException("unknown status " + mRecordingStatus);
}
} else {
switch (mRecordingStatus) {
case RECORDING_ON:
case RECORDING_RESUMED:
// stop recording
Log.d(TAG, "STOP recording");
mVideoEncoder.stopRecording();
mRecordingStatus = RECORDING_OFF;
break;
case RECORDING_OFF:
// yay
break;
default:
throw new RuntimeException("unknown status " + mRecordingStatus);
}
}
// Set the video encoder's texture name. We only need to do this once, but in the
// current implementation it has to happen after the video encoder is started, so
// we just do it here.
//
// TODO: be less lame.
mVideoEncoder.setTextureId(mTextureId);
// Tell the video encoder thread that a new frame is available.
// This will be ignored if we're not actually recording.
mVideoEncoder.frameAvailable(mSurfaceTexture);
if (mIncomingWidth <= 0 || mIncomingHeight <= 0) {
// Texture size isn't set yet. This is only used for the filters, but to be
// safe we can just skip drawing while we wait for the various races to resolve.
// (This seems to happen if you toggle the screen off/on with power button.)
Log.i(TAG, "Drawing before incoming texture size set; skipping");
return;
}
if (mIncomingSizeUpdated) {
mFullScreen.getProgram().setTexSize(mIncomingWidth, mIncomingHeight);
mIncomingSizeUpdated = false;
}
// Draw the video frame.
mSurfaceTexture.getTransformMatrix(mSTMatrix);
mFullScreen.drawFrame(mTextureId, mSTMatrix);
}
}