Skip to content

Instantly share code, notes, and snippets.

@tzafrir
Created September 12, 2017 06:55
Show Gist options
  • Save tzafrir/772a62c861ac603e8fdbac1738030bde to your computer and use it in GitHub Desktop.
Save tzafrir/772a62c861ac603e8fdbac1738030bde to your computer and use it in GitHub Desktop.
package tv.example;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
import android.graphics.YuvImage;
import android.opengl.EGL14;
import android.opengl.EGLConfig;
import android.opengl.EGLContext;
import android.opengl.EGLDisplay;
import android.opengl.EGLExt;
import android.opengl.EGLSurface;
import android.opengl.GLES20;
import android.opengl.GLUtils;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Surface;
import android.view.TextureView;
import com.twilio.video.I420Frame;
import com.twilio.video.VideoRenderer;
import org.webrtc.GlUtil;
import org.webrtc.RendererCommon;
import org.webrtc.ThreadUtils;
import org.webrtc.YuvConverter;
import java.io.ByteArrayOutputStream;
import java.nio.ByteBuffer;
import java.util.concurrent.CountDownLatch;
import static android.graphics.ImageFormat.NV21;
//
// Example to use
// SimpleTextureViewRenderer textureView = findViewById(R.id.renderer);
// textureView.init(new SimpleVideoDrawer());
// textureView.setMirror(false);
// ...
//
public class SimpleTextureViewRenderer extends TextureView
implements VideoRenderer, TextureView.SurfaceTextureListener {
public final class EglCore {
private static final String TAG = "EglCore";
public static final int FLAG_RECORDABLE = 0x01;
public static final int FLAG_TRY_GLES3 = 0x02;
private static final int EGL_RECORDABLE_ANDROID = 0x3142;
private EGLDisplay eglDisplay = EGL14.EGL_NO_DISPLAY;
private EGLContext eglContext = EGL14.EGL_NO_CONTEXT;
private EGLConfig eglConfig = null;
private int glVersion = -1;
public EglCore() {
this(null, 0);
}
public EglCore(EGLContext sharedContext, int flags) {
if (eglDisplay != EGL14.EGL_NO_DISPLAY) {
throw new RuntimeException("EGL already set up");
}
if (sharedContext == null) {
sharedContext = EGL14.EGL_NO_CONTEXT;
}
eglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
if (eglDisplay == EGL14.EGL_NO_DISPLAY) {
throw new RuntimeException("unable to get EGL14 display");
}
int[] version = new int[2];
if (!EGL14.eglInitialize(eglDisplay, version, 0, version, 1)) {
eglDisplay = null;
throw new RuntimeException("unable to initialize EGL14");
}
if ((flags & FLAG_TRY_GLES3) != 0) {
EGLConfig config = getConfig(flags, 3);
if (config != null) {
int[] attrib3_list = {
EGL14.EGL_CONTEXT_CLIENT_VERSION, 3,
EGL14.EGL_NONE
};
EGLContext context = EGL14.eglCreateContext(eglDisplay, config, sharedContext,
attrib3_list, 0);
if (EGL14.eglGetError() == EGL14.EGL_SUCCESS) {
eglConfig = config;
eglContext = context;
glVersion = 3;
}
}
}
if (eglContext == EGL14.EGL_NO_CONTEXT) {
EGLConfig config = getConfig(flags, 2);
if (config == null) {
throw new RuntimeException("Unable to find a suitable EGLConfig");
}
int[] attrib2_list = {
EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
EGL14.EGL_NONE
};
EGLContext context = EGL14.eglCreateContext(eglDisplay, config, sharedContext,
attrib2_list, 0);
checkEglError("eglCreateContext");
eglConfig = config;
eglContext = context;
glVersion = 2;
}
int[] values = new int[1];
EGL14.eglQueryContext(eglDisplay, eglContext, EGL14.EGL_CONTEXT_CLIENT_VERSION,
values, 0);
Log.d(TAG, "EGLContext created, client version " + values[0]);
}
private EGLConfig getConfig(int flags, int version) {
int renderableType = EGL14.EGL_OPENGL_ES2_BIT;
if (version >= 3) {
renderableType |= EGLExt.EGL_OPENGL_ES3_BIT_KHR;
}
int[] attribList = {
EGL14.EGL_RED_SIZE, 8,
EGL14.EGL_GREEN_SIZE, 8,
EGL14.EGL_BLUE_SIZE, 8,
EGL14.EGL_ALPHA_SIZE, 8,
EGL14.EGL_RENDERABLE_TYPE, renderableType,
EGL14.EGL_NONE, 0,
EGL14.EGL_NONE
};
if ((flags & FLAG_RECORDABLE) != 0) {
attribList[attribList.length - 3] = EGL_RECORDABLE_ANDROID;
attribList[attribList.length - 2] = 1;
}
EGLConfig[] configs = new EGLConfig[1];
int[] numConfigs = new int[1];
if (!EGL14.eglChooseConfig(eglDisplay, attribList, 0, configs, 0, configs.length,
numConfigs, 0)) {
Log.w(TAG, "unable to find RGB8888 / " + version + " EGLConfig");
return null;
}
return configs[0];
}
public void release() {
if (eglDisplay != EGL14.EGL_NO_DISPLAY) {
EGL14.eglMakeCurrent(eglDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE,
EGL14.EGL_NO_CONTEXT);
EGL14.eglDestroyContext(eglDisplay, eglContext);
EGL14.eglReleaseThread();
EGL14.eglTerminate(eglDisplay);
}
eglDisplay = EGL14.EGL_NO_DISPLAY;
eglContext = EGL14.EGL_NO_CONTEXT;
eglConfig = null;
}
@Override
protected void finalize() throws Throwable {
try {
if (eglDisplay != EGL14.EGL_NO_DISPLAY) {
Log.w(TAG, "WARNING: EglCore was not explicitly released -- state may be leaked");
release();
}
} finally {
super.finalize();
}
}
public void releaseSurface(EGLSurface eglSurface) {
EGL14.eglDestroySurface(eglDisplay, eglSurface);
}
public EGLSurface createWindowSurface(Object surface) {
if (!(surface instanceof Surface) && !(surface instanceof SurfaceTexture)) {
throw new RuntimeException("invalid surface: " + surface);
}
int[] surfaceAttribs = {
EGL14.EGL_NONE
};
EGLSurface eglSurface = EGL14.eglCreateWindowSurface(eglDisplay, eglConfig, surface,
surfaceAttribs, 0);
checkEglError("eglCreateWindowSurface");
if (eglSurface == null) {
throw new RuntimeException("surface was null");
}
return eglSurface;
}
public EGLSurface createOffscreenSurface(int width, int height) {
int[] surfaceAttribs = {
EGL14.EGL_WIDTH, width,
EGL14.EGL_HEIGHT, height,
EGL14.EGL_NONE
};
EGLSurface eglSurface = EGL14.eglCreatePbufferSurface(eglDisplay, eglConfig,
surfaceAttribs, 0);
checkEglError("eglCreatePbufferSurface");
if (eglSurface == null) {
throw new RuntimeException("surface was null");
}
return eglSurface;
}
public void makeCurrent(EGLSurface eglSurface) {
if (eglDisplay == EGL14.EGL_NO_DISPLAY) {
Log.d(TAG, "NOTE: makeCurrent w/o display");
}
if (!EGL14.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)) {
throw new RuntimeException("eglMakeCurrent failed");
}
}
public void makeCurrent(EGLSurface drawSurface, EGLSurface readSurface) {
if (eglDisplay == EGL14.EGL_NO_DISPLAY) {
Log.d(TAG, "NOTE: makeCurrent w/o display");
}
if (!EGL14.eglMakeCurrent(eglDisplay, drawSurface, readSurface, eglContext)) {
throw new RuntimeException("eglMakeCurrent(draw,read) failed");
}
}
public void makeNothingCurrent() {
if (!EGL14.eglMakeCurrent(eglDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE,
EGL14.EGL_NO_CONTEXT)) {
throw new RuntimeException("eglMakeCurrent failed");
}
}
public boolean swapBuffers(EGLSurface eglSurface) {
return EGL14.eglSwapBuffers(eglDisplay, eglSurface);
}
public void setPresentationTime(EGLSurface eglSurface, long nsecs) {
EGLExt.eglPresentationTimeANDROID(eglDisplay, eglSurface, nsecs);
}
public boolean isCurrent(EGLSurface eglSurface) {
return eglContext.equals(EGL14.eglGetCurrentContext()) &&
eglSurface.equals(EGL14.eglGetCurrentSurface(EGL14.EGL_DRAW));
}
public int querySurface(EGLSurface eglSurface, int what) {
int[] value = new int[1];
EGL14.eglQuerySurface(eglDisplay, eglSurface, what, value, 0);
return value[0];
}
public String queryString(int what) {
return EGL14.eglQueryString(eglDisplay, what);
}
public int getGlVersion() {
return glVersion;
}
private void checkEglError(String msg) {
int error;
if ((error = EGL14.eglGetError()) != EGL14.EGL_SUCCESS) {
throw new RuntimeException(msg + ": EGL error: 0x" + Integer.toHexString(error));
}
}
}
class WindowSurface {
protected SimpleTextureViewRenderer.EglCore eglCore;
private EGLSurface eglSurface = EGL14.EGL_NO_SURFACE;
private int width = -1;
private int height = -1;
private Surface surface;
private boolean releaseSurface;
public WindowSurface(SimpleTextureViewRenderer.EglCore eglCore, Surface surface, boolean releaseSurface) {
this.eglCore = eglCore;
createWindowSurface(surface);
this.surface = surface;
this.releaseSurface = releaseSurface;
}
public WindowSurface(EglCore eglCore, SurfaceTexture surfaceTexture) {
this.eglCore = eglCore;
createWindowSurface(surfaceTexture);
}
public void createWindowSurface(Object surface) {
if (eglSurface != EGL14.EGL_NO_SURFACE) {
throw new IllegalStateException("surface already created");
}
eglSurface = eglCore.createWindowSurface(surface);
}
public void createOffscreenSurface(int width, int height) {
if (eglSurface != EGL14.EGL_NO_SURFACE) {
throw new IllegalStateException("surface already created");
}
eglSurface = eglCore.createOffscreenSurface(width, height);
this.width = width;
this.height = height;
}
public int getWidth() {
if (width < 0) {
return eglCore.querySurface(eglSurface, EGL14.EGL_WIDTH);
} else {
return width;
}
}
public int getHeight() {
if (height < 0) {
return eglCore.querySurface(eglSurface, EGL14.EGL_HEIGHT);
} else {
return height;
}
}
public void releaseEglSurface() {
eglCore.releaseSurface(eglSurface);
eglSurface = EGL14.EGL_NO_SURFACE;
width = height = -1;
}
public void makeCurrent() {
eglCore.makeCurrent(eglSurface);
}
public void makeCurrentReadFrom(WindowSurface readSurface) {
eglCore.makeCurrent(eglSurface, readSurface.eglSurface);
}
public boolean swapBuffers() {
boolean result = eglCore.swapBuffers(eglSurface);
if (!result) {
Log.d(TAG, "WARNING: swapBuffers() failed");
}
return result;
}
public void release() {
releaseEglSurface();
if (surface != null) {
if (releaseSurface) {
surface.release();
}
surface = null;
}
}
public void recreate(EglCore newEglCore) {
if (surface == null) {
throw new RuntimeException("not yet implemented for SurfaceTexture");
}
eglCore = newEglCore;
createWindowSurface(surface);
}
}
private static final String TAG = SimpleTextureViewRenderer.class.getCanonicalName();
private final RendererCommon.YuvUploader yuvUploader = new RendererCommon.YuvUploader();
private final Runnable renderFrameRunnable;
private Handler renderThreadHandler;
private HandlerThread renderThread;
private final Object frameLock = new Object();
private final Object layoutLock = new Object();
private final Object handlerLock = new Object();
private RendererCommon.GlDrawer drawer;
private EglCore eglCore;
private WindowSurface windowSurface;
private SurfaceTexture surfaceTexture;
private YuvConverter yuvConverter = null;
private ByteBuffer outputFrameBuffer = null;
private I420Frame pendingFrame;
private int rgbTexture = 0;
private int[] yuvTextures = null;
private float[] texMatrix;
private boolean isSurfaceCreated;
private boolean mirror;
private int frameWidth;
private int frameHeight;
private int frameRotation;
private final Point layoutSize = new Point();
private final Point surfaceSize = new Point();
private Point desiredLayoutSize = new Point();
public SimpleTextureViewRenderer(Context context) {
super(context);
this.renderFrameRunnable = new Runnable() {
public void run() {
SimpleTextureViewRenderer.this.renderFrameOnRenderThread();
}
};
setSurfaceTextureListener(this);
setOpaque(false);
}
public SimpleTextureViewRenderer(Context context, AttributeSet attrs) {
super(context, attrs);
this.renderFrameRunnable = new Runnable() {
public void run() {
SimpleTextureViewRenderer.this.renderFrameOnRenderThread();
}
};
setSurfaceTextureListener(this);
setOpaque(false);
}
public void init(RendererCommon.GlDrawer drawer) {
Object lockObject = this.handlerLock;
synchronized(this.handlerLock) {
if (this.renderThreadHandler != null) {
throw new IllegalStateException(TAG + " Already initialized");
}
this.drawer = drawer;
this.renderThread = new HandlerThread(TAG);
this.renderThread.start();
this.renderThreadHandler = new Handler(this.renderThread.getLooper());
}
createEglSurface();
}
private void createEglSurface() {
this.runOnRenderThread(new Runnable() {
public void run() {
synchronized(SimpleTextureViewRenderer.this.layoutLock) {
if (SimpleTextureViewRenderer.this.isSurfaceCreated) {
eglCore = new EglCore(null, EglCore.FLAG_TRY_GLES3);
windowSurface = new WindowSurface(eglCore, surfaceTexture);
windowSurface.makeCurrent();
GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1);
}
}
}
});
}
public void release() {
final CountDownLatch eglCleanupBarrier = new CountDownLatch(1);
Object lockObject = this.handlerLock;
synchronized(this.handlerLock) {
if (this.renderThreadHandler == null) {
return;
}
this.renderThreadHandler.postAtFrontOfQueue(new Runnable() {
public void run() {
if (yuvConverter != null) {
yuvConverter.release();
}
SimpleTextureViewRenderer.this.drawer.release();
SimpleTextureViewRenderer.this.drawer = null;
if(SimpleTextureViewRenderer.this.yuvTextures != null) {
GLES20.glDeleteTextures(3, SimpleTextureViewRenderer.this.yuvTextures, 0);
SimpleTextureViewRenderer.this.yuvTextures = null;
}
SimpleTextureViewRenderer.this.windowSurface.release();
SimpleTextureViewRenderer.this.eglCore.release();
Log.i(TAG, "Releasing SurfaceTexture in renderer thread");
surfaceTexture.release();
eglCleanupBarrier.countDown();
}
});
this.renderThreadHandler = null;
}
ThreadUtils.awaitUninterruptibly(eglCleanupBarrier);
this.renderThread.quit();
lockObject = this.frameLock;
synchronized(this.frameLock) {
if(this.pendingFrame != null) {
this.pendingFrame.release();
this.pendingFrame = null;
}
}
ThreadUtils.joinUninterruptibly(this.renderThread);
this.renderThread = null;
lockObject = this.layoutLock;
synchronized(this.layoutLock) {
this.frameWidth = 0;
this.frameHeight = 0;
this.frameRotation = 0;
}
}
public void setMirror(boolean mirror) {
Object lockObject = this.layoutLock;
synchronized(this.layoutLock) {
this.mirror = mirror;
}
}
public void renderFrame(I420Frame frame) {
Object handleLockObject = this.handlerLock;
synchronized(this.handlerLock) {
if (this.renderThreadHandler == null) {
frame.release();
} else {
Object frameLockObject = this.frameLock;
synchronized(this.frameLock) {
if(this.pendingFrame != null) {
this.pendingFrame.release();
}
this.pendingFrame = frame;
if (frame != null) {
int width = frame.rotatedWidth();
int height = frame.rotatedHeight();
int stride = width;
float aspectRatio = this.frameAspectRatio();
Object layoutLockObject = this.layoutLock;
synchronized(this.layoutLock) {
float[] rotatedSamplingMatrix = RendererCommon.rotateTextureMatrix(
frame.samplingMatrix,
(float)frame.rotationDegree);
float[] layoutMatrix = RendererCommon.getLayoutMatrix(
this.mirror,
aspectRatio,
(float)this.layoutSize.x / (float)this.layoutSize.y);
this.texMatrix = RendererCommon.multiplyMatrices(
rotatedSamplingMatrix,
layoutMatrix);
}
if (frame.yuvPlanes == null && width > 0 && height > 0 && frame.textureId > 0) {
int outputFrameSize = width * height * 3 / 2;
if (this.outputFrameBuffer == null) {
this.outputFrameBuffer = ByteBuffer.allocateDirect(outputFrameSize);
}
if (yuvConverter == null) {
yuvConverter = new YuvConverter();
}
yuvConverter.convert(this.outputFrameBuffer,
width, height, stride,
frame.textureId,
texMatrix);
}
}
updateFrameDimensions(frame);
renderThreadHandler.post(this.renderFrameRunnable);
}
}
}
}
private void runOnRenderThread(Runnable runnable) {
Object lockObject = this.handlerLock;
synchronized(this.handlerLock) {
if (this.renderThreadHandler != null) {
this.renderThreadHandler.post(runnable);
}
}
}
private void renderFrameOnRenderThread() {
if (Thread.currentThread() != this.renderThread) {
throw new IllegalStateException(TAG + " Wrong thread.");
} else {
Object startTimeNs = this.frameLock;
I420Frame frame;
synchronized(this.frameLock) {
if(this.pendingFrame == null) {
return;
}
frame = this.pendingFrame;
this.pendingFrame = null;
}
int width = frame.rotatedWidth();
int height = frame.rotatedHeight();
int stride = width;
float aspectRatio = this.frameAspectRatio();
if (this.windowSurface != null) {
if (!this.checkConsistentLayout()) {
frame.release();
} else {
Object lockObject = this.layoutLock;
if (frame.yuvPlanes != null) {
if (texMatrix != null) {
draw(frame.width, frame.height, frame.yuvStrides, frame.yuvPlanes, texMatrix, null);
}
} else if (frame.yuvPlanes == null && width > 0 && height > 0 && frame.textureId > 0) {
if (this.outputFrameBuffer != null) {
ByteBuffer[] yuvPlanes;
int[] yuvStrides;
lockObject = this.layoutLock;
synchronized (this.layoutLock) {
byte[] data = this.outputFrameBuffer.array();
int offset = this.outputFrameBuffer.arrayOffset();
yuvPlanes = new ByteBuffer[]{
ByteBuffer.allocateDirect(width * height),
ByteBuffer.allocateDirect(width * height / 4),
ByteBuffer.allocateDirect(width * height / 4)
};
yuvStrides = new int[]{
width,
(width + 1) / 2,
(width + 1) / 2
};
yuvPlanes[0].put(data, offset, width * height);
for (int r = height; r < height * 3 / 2; ++r) {
yuvPlanes[1].put(data, offset + r * stride, stride / 2);
}
for (int r = height; r < height * 3 / 2; ++r) {
yuvPlanes[2].put(data, offset + r * stride + stride / 2, stride / 2);
}
}
YuvImage yuvImage = i420ToYuvImage(yuvPlanes, yuvStrides, width, height);
Rect rect = new Rect(0, 0, yuvImage.getWidth(), yuvImage.getHeight());
ByteArrayOutputStream stream = new ByteArrayOutputStream();
yuvImage.compressToJpeg(rect, 100, stream);
byte[] imageBytes = stream.toByteArray();
final Bitmap bm = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length);
lockObject = this.layoutLock;
float[] tm;
synchronized (this.layoutLock) {
final float[] rotatedSamplingMatrix = RendererCommon.rotateTextureMatrix(
frame.samplingMatrix, 270f);
final float[] layoutMatrix = RendererCommon.getLayoutMatrix(
false,
aspectRatio,
(float) this.layoutSize.x / (float) this.layoutSize.y);
tm = RendererCommon.multiplyMatrices(
rotatedSamplingMatrix,
layoutMatrix);
}
draw(width, height, yuvStrides, yuvPlanes, tm, bm);
}
}
this.windowSurface.swapBuffers();
frame.release();
}
} else {
frame.release();
}
}
}
private void draw(int width, int height,
int[] yuvStrides, ByteBuffer[] yuvPlanes,
float[] texMatrix, Bitmap bitmap) {
if (bitmap == null) {
if (this.yuvTextures == null) {
this.yuvTextures = new int[3];
for (int i = 0; i < 3; ++i) {
this.yuvTextures[i] = GlUtil.generateTexture(GLES20.GL_TEXTURE_2D);
}
}
} else if (rgbTexture == 0) {
rgbTexture = GlUtil.generateTexture(GLES20.GL_TEXTURE_2D);
}
if (bitmap == null) {
this.yuvUploader.uploadYuvData(this.yuvTextures, width, height, yuvStrides, yuvPlanes);
} else {
GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + 0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, rgbTexture);
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, bitmap, 0);
}
GLES20.glClearColor(0f, 0f, 0f, 0f);
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
if (bitmap == null) {
this.drawer.drawYuv(
this.yuvTextures, texMatrix,
0, 0, 0, 0,
this.surfaceSize.x, this.surfaceSize.y);
} else {
this.drawer.drawRgb(
rgbTexture, texMatrix,
this.frameWidth, this.frameHeight,
0, 0, this.surfaceSize.x, this.surfaceSize.y);
}
}
private YuvImage i420ToYuvImage(ByteBuffer[] yuvPlanes,
int[] yuvStrides,
int width,
int height) {
if (yuvStrides[0] != width) {
return fastI420ToYuvImage(yuvPlanes, yuvStrides, width, height);
}
if (yuvStrides[1] != width / 2) {
return fastI420ToYuvImage(yuvPlanes, yuvStrides, width, height);
}
if (yuvStrides[2] != width / 2) {
return fastI420ToYuvImage(yuvPlanes, yuvStrides, width, height);
}
byte[] bytes = new byte[yuvStrides[0] * height +
yuvStrides[1] * height / 2 +
yuvStrides[2] * height / 2];
ByteBuffer tmp = ByteBuffer.wrap(bytes, 0, width * height);
copyPlane(yuvPlanes[0], tmp);
byte[] tmpBytes = new byte[width / 2 * height / 2];
tmp = ByteBuffer.wrap(tmpBytes, 0, width / 2 * height / 2);
copyPlane(yuvPlanes[2], tmp);
for (int row = 0 ; row < height / 2 ; row++) {
for (int col = 0 ; col < width / 2 ; col++) {
bytes[width * height + row * width + col * 2]
= tmpBytes[row * width / 2 + col];
}
}
copyPlane(yuvPlanes[1], tmp);
for (int row = 0 ; row < height / 2 ; row++) {
for (int col = 0 ; col < width / 2 ; col++) {
bytes[width * height + row * width + col * 2 + 1] =
tmpBytes[row * width / 2 + col];
}
}
return new YuvImage(bytes, NV21, width, height, null);
}
private YuvImage fastI420ToYuvImage(ByteBuffer[] yuvPlanes,
int[] yuvStrides,
int width,
int height) {
byte[] bytes = new byte[width * height * 3 / 2];
int i = 0;
for (int row = 0 ; row < height ; row++) {
for (int col = 0 ; col < width ; col++) {
bytes[i++] = yuvPlanes[0].get(col + row * yuvStrides[0]);
}
}
for (int row = 0 ; row < height / 2 ; row++) {
for (int col = 0 ; col < width / 2; col++) {
bytes[i++] = yuvPlanes[2].get(col + row * yuvStrides[2]);
bytes[i++] = yuvPlanes[1].get(col + row * yuvStrides[1]);
}
}
return new YuvImage(bytes, NV21, width, height, null);
}
private void copyPlane(ByteBuffer src, ByteBuffer dst) {
src.position(0).limit(src.capacity());
dst.put(src);
dst.position(0).limit(dst.capacity());
}
private boolean checkConsistentLayout() {
if (Thread.currentThread() != this.renderThread) {
throw new IllegalStateException(TAG + " Wrong thread.");
} else {
Object lockObject = this.layoutLock;
synchronized(this.layoutLock) {
return this.layoutSize.equals(this.desiredLayoutSize) && this.surfaceSize.equals(this.layoutSize);
}
}
}
private float frameAspectRatio() {
Object lockObject = this.layoutLock;
synchronized (this.layoutLock) {
return (this.frameWidth != 0 && this.frameHeight != 0) ?
((this.frameRotation % 180 == 0) ?
((float)this.frameWidth / (float)this.frameHeight) :
((float)this.frameHeight / (float)this.frameWidth)) :
0.0F;
}
}
private void updateFrameDimensions(I420Frame frame) {
Object var2 = this.layoutLock;
synchronized(this.layoutLock) {
if (this.frameWidth != frame.width ||
this.frameHeight != frame.height ||
this.frameRotation != frame.rotationDegree) {
this.frameWidth = frame.width;
this.frameHeight = frame.height;
this.frameRotation = frame.rotationDegree;
this.post(new Runnable() {
public void run() {
SimpleTextureViewRenderer.this.requestLayout();
}
});
}
}
}
@Override
public void onSurfaceTextureAvailable(SurfaceTexture st, int width, int height) {
Object lockObject = this.layoutLock;
synchronized (this.layoutLock) {
this.surfaceTexture = st;
this.isSurfaceCreated = true;
this.surfaceSize.x = width;
this.surfaceSize.y = height;
}
createEglSurface();
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture st, int width, int height) {
Object lockObject = this.layoutLock;
synchronized(this.layoutLock) {
this.surfaceSize.x = width;
this.surfaceSize.y = height;
}
runOnRenderThread(this.renderFrameRunnable);
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture st) {
Object lockObject = this.layoutLock;
synchronized (this.layoutLock) {
this.surfaceTexture = null;
this.surfaceSize.x = 0;
this.surfaceSize.y = 0;
}
return true;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture st) {
}
}
package tv.example;
import android.opengl.GLES11Ext;
import android.opengl.GLES20;
import org.webrtc.GlShader;
import org.webrtc.GlUtil;
import org.webrtc.RendererCommon;
import java.nio.FloatBuffer;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.Map;
public class SimpleVideoDrawer implements RendererCommon.GlDrawer {
private static final String VERTEX_SHADER_STRING =
"varying vec2 interp_tc;\n" +
"attribute vec4 in_pos;\n" +
"attribute vec4 in_tc;\n" +
"uniform mat4 texMatrix;\n" +
"void main() {\n" +
" gl_Position = in_pos;\n" +
" interp_tc = (texMatrix * in_tc).xy;\n" +
"}\n";
private static final String YUV_FRAGMENT_SHADER_STRING =
"precision mediump float;\n" +
"varying vec2 interp_tc;\n" +
"uniform sampler2D y_tex;\n" +
"uniform sampler2D u_tex;\n" +
"uniform sampler2D v_tex;\n" +
"void main() {\n" +
" float y = texture2D(y_tex, interp_tc).r;\n" +
" float u = texture2D(u_tex, interp_tc).r - 0.5;\n" +
" float v = texture2D(v_tex, interp_tc).r - 0.5;\n" +
" gl_FragColor = vec4(y + 1.403 * v, y - 0.344 * u - 0.714 * v, y + 1.77 * u, 1);\n" +
"}\n";
private static final String RGB_FRAGMENT_SHADER_STRING =
"precision mediump float;\n" +
"varying vec2 interp_tc;\n" +
"uniform sampler2D rgb_tex;\n" +
"void main() {\n" +
" gl_FragColor = texture2D(rgb_tex, interp_tc);\n" +
"}\n";
private static final String OES_FRAGMENT_SHADER_STRING =
"#extension GL_OES_EGL_image_external : require\n" +
"precision mediump float;\n" +
"varying vec2 interp_tc;\n" +
"uniform samplerExternalOES oes_tex;\n" +
"void main() {\n" +
" gl_FragColor = texture2D(oes_tex, interp_tc);\n" +
"}\n";
enum RenderType {
OES, RGB, YUV
}
private static final FloatBuffer FULL_RECTANGLE_BUF = GlUtil.createFloatBuffer(
new float[] {
-1.0F, -1.0F, 1.0F, -1.0F, -1.0F, 1.0F, 1.0F, 1.0F
});
private static final FloatBuffer FULL_RECTANGLE_TEX_BUF = GlUtil.createFloatBuffer(
new float[] {
0.0F, 0.0F, 1.0F, 0.0F, 0.0F, 1.0F, 1.0F, 1.0F
});
private final Map<String, SimpleVideoDrawer.Shader> shaders = new IdentityHashMap();
public SimpleVideoDrawer() {
}
public void drawOes(int oesTextureId, float[] texMatrix,
int frameWidth, int frameHeight,
int viewportX, int viewportY,
int viewportWidth, int viewportHeight) {
if (!this.prepareShader(LocalVideoDrawer.RenderType.OES,
VERTEX_SHADER_STRING, OES_FRAGMENT_SHADER_STRING, texMatrix)) {
return;
}
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, oesTextureId);
this.drawRectangle(viewportX, viewportY, viewportWidth, viewportHeight);
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0);
}
public void drawRgb(int textureId, float[] texMatrix,
int frameWidth, int frameHeight,
int viewportX, int viewportY,
int viewportWidth, int viewportHeight) {
if (!this.prepareShader(LocalVideoDrawer.RenderType.RGB,
VERTEX_SHADER_STRING, RGB_FRAGMENT_SHADER_STRING, texMatrix)) {
return;
}
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);
this.drawRectangle(viewportX, viewportY, viewportWidth, viewportHeight);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
}
public void drawYuv(int[] yuvTextures, float[] texMatrix,
int frameWidth, int frameHeight,
int viewportX, int viewportY,
int viewportWidth, int viewportHeight) {
if (!this.prepareShader(LocalVideoDrawer.RenderType.YUV,
VERTEX_SHADER_STRING, YUV_FRAGMENT_SHADER_STRING, texMatrix)) {
return;
}
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, yuvTextures[0]);
GLES20.glActiveTexture(GLES20.GL_TEXTURE1);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, yuvTextures[1]);
GLES20.glActiveTexture(GLES20.GL_TEXTURE2);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, yuvTextures[2]);
this.drawRectangle(viewportX, viewportY, viewportWidth, viewportHeight);
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
GLES20.glActiveTexture(GLES20.GL_TEXTURE1);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
GLES20.glActiveTexture(GLES20.GL_TEXTURE2);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
}
private void drawRectangle(int x, int y, int width, int height) {
GLES20.glViewport(x, y, width, height);
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
}
private boolean prepareShader(LocalVideoDrawer.RenderType type, String vertexShader, String fragmentShader, float[] texMatrix) {
SimpleVideoDrawer.Shader shader;
if (this.shaders.containsKey(fragmentShader)) {
shader = this.shaders.get(fragmentShader);
} else {
shader = new SimpleVideoDrawer.Shader(type, vertexShader, fragmentShader);
this.shaders.put(fragmentShader, shader);
shader.glShader.useProgram();
if (type == LocalVideoDrawer.RenderType.YUV) {
GLES20.glUniform1i(shader.glShader.getUniformLocation("y_tex"), 0);
GLES20.glUniform1i(shader.glShader.getUniformLocation("u_tex"), 1);
GLES20.glUniform1i(shader.glShader.getUniformLocation("v_tex"), 2);
} else if (type == LocalVideoDrawer.RenderType.RGB) {
GLES20.glUniform1i(shader.glShader.getUniformLocation("rgb_tex"), 0);
} else {
if (type != LocalVideoDrawer.RenderType.OES) {
throw new IllegalStateException("Unknown fragment shader: " + fragmentShader);
}
GLES20.glUniform1i(shader.glShader.getUniformLocation("oes_tex"), 0);
}
GlUtil.checkNoGLES2Error("Initialize fragment shader uniform values.");
shader.glShader.setVertexAttribArray("in_pos", 2, FULL_RECTANGLE_BUF);
shader.glShader.setVertexAttribArray("in_tc", 2, FULL_RECTANGLE_TEX_BUF);
}
try {
shader.glShader.useProgram();
} catch (RuntimeException e) {
return false;
}
GLES20.glUniformMatrix4fv(shader.texMatrixLocation, 1, false, texMatrix, 0);
return true;
}
public void release() {
Iterator it = this.shaders.values().iterator();
while (it.hasNext()) {
SimpleVideoDrawer.Shader shader = (SimpleVideoDrawer.Shader)it.next();
shader.glShader.release();
}
this.shaders.clear();
}
private static class Shader {
public final GlShader glShader;
public int texMatrixLocation;
public int texMaskMatrixLocation;
public Shader(LocalVideoDrawer.RenderType type, String vertexShader, String fragmentShader) {
this.glShader = new GlShader(vertexShader, fragmentShader);
this.texMatrixLocation = this.glShader.getUniformLocation("texMatrix");
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment