Created
September 12, 2017 06:55
-
-
Save tzafrir/772a62c861ac603e8fdbac1738030bde to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) { | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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