| /* |
| * libjingle |
| * Copyright 2014 Google Inc. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright notice, |
| * this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright notice, |
| * this list of conditions and the following disclaimer in the documentation |
| * and/or other materials provided with the distribution. |
| * 3. The name of the author may not be used to endorse or promote products |
| * derived from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED |
| * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF |
| * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO |
| * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; |
| * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, |
| * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR |
| * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
| * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| package org.webrtc; |
| |
| import java.util.ArrayList; |
| import java.util.concurrent.CountDownLatch; |
| import java.util.concurrent.LinkedBlockingQueue; |
| |
| import javax.microedition.khronos.egl.EGLConfig; |
| import javax.microedition.khronos.opengles.GL10; |
| |
| import android.annotation.SuppressLint; |
| import android.graphics.Point; |
| import android.graphics.Rect; |
| import android.graphics.SurfaceTexture; |
| import android.opengl.EGL14; |
| import android.opengl.EGLContext; |
| import android.opengl.GLES20; |
| import android.opengl.GLSurfaceView; |
| import android.util.Log; |
| |
| import org.webrtc.VideoRenderer.I420Frame; |
| |
| /** |
| * Efficiently renders YUV frames using the GPU for CSC. |
| * Clients will want first to call setView() to pass GLSurfaceView |
| * and then for each video stream either create instance of VideoRenderer using |
| * createGui() call or VideoRenderer.Callbacks interface using create() call. |
| * Only one instance of the class can be created. |
| */ |
| public class VideoRendererGui implements GLSurfaceView.Renderer { |
| // |instance|, |instance.surface|, |eglContext|, and |eglContextReady| are synchronized on |
| // |VideoRendererGui.class|. |
| private static VideoRendererGui instance = null; |
| private static Runnable eglContextReady = null; |
| private static final String TAG = "VideoRendererGui"; |
| private GLSurfaceView surface; |
| private static EGLContext eglContext = null; |
| // Indicates if SurfaceView.Renderer.onSurfaceCreated was called. |
| // If true then for every newly created yuv image renderer createTexture() |
| // should be called. The variable is accessed on multiple threads and |
| // all accesses are synchronized on yuvImageRenderers' object lock. |
| private boolean onSurfaceCreatedCalled; |
| private int screenWidth; |
| private int screenHeight; |
| // List of yuv renderers. |
| private final ArrayList<YuvImageRenderer> yuvImageRenderers; |
| // |drawer| is synchronized on |yuvImageRenderers|. |
| private GlRectDrawer drawer; |
| private static final int EGL14_SDK_VERSION = |
| android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; |
| // Current SDK version. |
| private static final int CURRENT_SDK_VERSION = |
| android.os.Build.VERSION.SDK_INT; |
| |
| private VideoRendererGui(GLSurfaceView surface) { |
| this.surface = surface; |
| // Create an OpenGL ES 2.0 context. |
| surface.setPreserveEGLContextOnPause(true); |
| surface.setEGLContextClientVersion(2); |
| surface.setRenderer(this); |
| surface.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); |
| |
| yuvImageRenderers = new ArrayList<YuvImageRenderer>(); |
| } |
| |
| /** |
| * Class used to display stream of YUV420 frames at particular location |
| * on a screen. New video frames are sent to display using renderFrame() |
| * call. |
| */ |
| private static class YuvImageRenderer implements VideoRenderer.Callbacks { |
| // |surface| is synchronized on |this|. |
| private GLSurfaceView surface; |
| private int id; |
| private int[] yuvTextures = { -1, -1, -1 }; |
| private int oesTexture = -1; |
| |
| // Render frame queue - accessed by two threads. renderFrame() call does |
| // an offer (writing I420Frame to render) and early-returns (recording |
| // a dropped frame) if that queue is full. draw() call does a peek(), |
| // copies frame to texture and then removes it from a queue using poll(). |
| private final LinkedBlockingQueue<I420Frame> frameToRenderQueue; |
| // Local copy of incoming video frame. Synchronized on |frameToRenderQueue|. |
| private I420Frame yuvFrameToRender; |
| private I420Frame textureFrameToRender; |
| // Type of video frame used for recent frame rendering. |
| private static enum RendererType { RENDERER_YUV, RENDERER_TEXTURE }; |
| private RendererType rendererType; |
| private RendererCommon.ScalingType scalingType; |
| private boolean mirror; |
| private RendererCommon.RendererEvents rendererEvents; |
| // Flag if renderFrame() was ever called. |
| boolean seenFrame; |
| // Total number of video frames received in renderFrame() call. |
| private int framesReceived; |
| // Number of video frames dropped by renderFrame() because previous |
| // frame has not been rendered yet. |
| private int framesDropped; |
| // Number of rendered video frames. |
| private int framesRendered; |
| // Time in ns when the first video frame was rendered. |
| private long startTimeNs = -1; |
| // Time in ns spent in draw() function. |
| private long drawTimeNs; |
| // Time in ns spent in renderFrame() function - including copying frame |
| // data to rendering planes. |
| private long copyTimeNs; |
| // The allowed view area in percentage of screen size. |
| private final Rect layoutInPercentage; |
| // The actual view area in pixels. It is a centered subrectangle of the rectangle defined by |
| // |layoutInPercentage|. |
| private final Rect displayLayout = new Rect(); |
| // Cached texture transformation matrix, calculated from current layout parameters. |
| private final float[] texMatrix = new float[16]; |
| // Flag if texture vertices or coordinates update is needed. |
| private boolean updateTextureProperties; |
| // Texture properties update lock. |
| private final Object updateTextureLock = new Object(); |
| // Viewport dimensions. |
| private int screenWidth; |
| private int screenHeight; |
| // Video dimension. |
| private int videoWidth; |
| private int videoHeight; |
| |
| // This is the degree that the frame should be rotated clockwisely to have |
| // it rendered up right. |
| private int rotationDegree; |
| |
| private YuvImageRenderer( |
| GLSurfaceView surface, int id, |
| int x, int y, int width, int height, |
| RendererCommon.ScalingType scalingType, boolean mirror) { |
| Log.d(TAG, "YuvImageRenderer.Create id: " + id); |
| this.surface = surface; |
| this.id = id; |
| this.scalingType = scalingType; |
| this.mirror = mirror; |
| frameToRenderQueue = new LinkedBlockingQueue<I420Frame>(1); |
| layoutInPercentage = new Rect(x, y, Math.min(100, x + width), Math.min(100, y + height)); |
| updateTextureProperties = false; |
| rotationDegree = 0; |
| } |
| |
| private synchronized void release() { |
| surface = null; |
| synchronized (frameToRenderQueue) { |
| frameToRenderQueue.clear(); |
| yuvFrameToRender = null; |
| textureFrameToRender = null; |
| } |
| } |
| |
| private void createTextures() { |
| Log.d(TAG, " YuvImageRenderer.createTextures " + id + " on GL thread:" + |
| Thread.currentThread().getId()); |
| |
| // Generate 3 texture ids for Y/U/V and place them into |yuvTextures|. |
| GLES20.glGenTextures(3, yuvTextures, 0); |
| for (int i = 0; i < 3; i++) { |
| GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + i); |
| GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, yuvTextures[i]); |
| GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, |
| GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR); |
| GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, |
| GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); |
| GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, |
| GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); |
| GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, |
| GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); |
| } |
| GlUtil.checkNoGLES2Error("y/u/v glGenTextures"); |
| } |
| |
| private void checkAdjustTextureCoords() { |
| synchronized(updateTextureLock) { |
| if (!updateTextureProperties) { |
| return; |
| } |
| // Initialize to maximum allowed area. Round to integer coordinates inwards the layout |
| // bounding box (ceil left/top and floor right/bottom) to not break constraints. |
| displayLayout.set( |
| (screenWidth * layoutInPercentage.left + 99) / 100, |
| (screenHeight * layoutInPercentage.top + 99) / 100, |
| (screenWidth * layoutInPercentage.right) / 100, |
| (screenHeight * layoutInPercentage.bottom) / 100); |
| Log.d(TAG, "ID: " + id + ". AdjustTextureCoords. Allowed display size: " |
| + displayLayout.width() + " x " + displayLayout.height() + ". Video: " + videoWidth |
| + " x " + videoHeight + ". Rotation: " + rotationDegree + ". Mirror: " + mirror); |
| final float videoAspectRatio = (rotationDegree % 180 == 0) |
| ? (float) videoWidth / videoHeight |
| : (float) videoHeight / videoWidth; |
| // Adjust display size based on |scalingType|. |
| final Point displaySize = RendererCommon.getDisplaySize(scalingType, |
| videoAspectRatio, displayLayout.width(), displayLayout.height()); |
| displayLayout.inset((displayLayout.width() - displaySize.x) / 2, |
| (displayLayout.height() - displaySize.y) / 2); |
| Log.d(TAG, " Adjusted display size: " + displayLayout.width() + " x " |
| + displayLayout.height()); |
| RendererCommon.getTextureMatrix(texMatrix, rotationDegree, mirror, videoAspectRatio, |
| (float) displayLayout.width() / displayLayout.height()); |
| updateTextureProperties = false; |
| Log.d(TAG, " AdjustTextureCoords done"); |
| } |
| } |
| |
| private void draw(GlRectDrawer drawer) { |
| if (!seenFrame) { |
| // No frame received yet - nothing to render. |
| return; |
| } |
| long now = System.nanoTime(); |
| |
| // OpenGL defaults to lower left origin. |
| GLES20.glViewport(displayLayout.left, screenHeight - displayLayout.bottom, |
| displayLayout.width(), displayLayout.height()); |
| |
| I420Frame frameFromQueue; |
| synchronized (frameToRenderQueue) { |
| // Check if texture vertices/coordinates adjustment is required when |
| // screen orientation changes or video frame size changes. |
| checkAdjustTextureCoords(); |
| |
| frameFromQueue = frameToRenderQueue.peek(); |
| if (frameFromQueue != null && startTimeNs == -1) { |
| startTimeNs = now; |
| } |
| |
| if (frameFromQueue != null) { |
| if (frameFromQueue.yuvFrame) { |
| // YUV textures rendering. Upload YUV data as textures. |
| for (int i = 0; i < 3; ++i) { |
| GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + i); |
| GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, yuvTextures[i]); |
| int w = (i == 0) ? frameFromQueue.width : frameFromQueue.width / 2; |
| int h = (i == 0) ? frameFromQueue.height : frameFromQueue.height / 2; |
| GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE, |
| w, h, 0, GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE, |
| frameFromQueue.yuvPlanes[i]); |
| } |
| } else { |
| // External texture rendering. Copy texture id and update texture image to latest. |
| // TODO(magjed): We should not make an unmanaged copy of texture id. Also, this is not |
| // the best place to call updateTexImage. |
| oesTexture = frameFromQueue.textureId; |
| if (frameFromQueue.textureObject instanceof SurfaceTexture) { |
| SurfaceTexture surfaceTexture = |
| (SurfaceTexture) frameFromQueue.textureObject; |
| surfaceTexture.updateTexImage(); |
| } |
| } |
| |
| frameToRenderQueue.poll(); |
| } |
| } |
| |
| if (rendererType == RendererType.RENDERER_YUV) { |
| drawer.drawYuv(videoWidth, videoHeight, yuvTextures, texMatrix); |
| } else { |
| drawer.drawOes(oesTexture, texMatrix); |
| } |
| |
| if (frameFromQueue != null) { |
| framesRendered++; |
| drawTimeNs += (System.nanoTime() - now); |
| if ((framesRendered % 300) == 0) { |
| logStatistics(); |
| } |
| } |
| } |
| |
| private void logStatistics() { |
| long timeSinceFirstFrameNs = System.nanoTime() - startTimeNs; |
| Log.d(TAG, "ID: " + id + ". Type: " + rendererType + |
| ". Frames received: " + framesReceived + |
| ". Dropped: " + framesDropped + ". Rendered: " + framesRendered); |
| if (framesReceived > 0 && framesRendered > 0) { |
| Log.d(TAG, "Duration: " + (int)(timeSinceFirstFrameNs / 1e6) + |
| " ms. FPS: " + (float)framesRendered * 1e9 / timeSinceFirstFrameNs); |
| Log.d(TAG, "Draw time: " + |
| (int) (drawTimeNs / (1000 * framesRendered)) + " us. Copy time: " + |
| (int) (copyTimeNs / (1000 * framesReceived)) + " us"); |
| } |
| } |
| |
| public void setScreenSize(final int screenWidth, final int screenHeight) { |
| synchronized(updateTextureLock) { |
| if (screenWidth == this.screenWidth && screenHeight == this.screenHeight) { |
| return; |
| } |
| Log.d(TAG, "ID: " + id + ". YuvImageRenderer.setScreenSize: " + |
| screenWidth + " x " + screenHeight); |
| this.screenWidth = screenWidth; |
| this.screenHeight = screenHeight; |
| updateTextureProperties = true; |
| } |
| } |
| |
| public void setPosition(int x, int y, int width, int height, |
| RendererCommon.ScalingType scalingType, boolean mirror) { |
| final Rect layoutInPercentage = |
| new Rect(x, y, Math.min(100, x + width), Math.min(100, y + height)); |
| synchronized(updateTextureLock) { |
| if (layoutInPercentage.equals(this.layoutInPercentage) && scalingType == this.scalingType |
| && mirror == this.mirror) { |
| return; |
| } |
| Log.d(TAG, "ID: " + id + ". YuvImageRenderer.setPosition: (" + x + ", " + y + |
| ") " + width + " x " + height + ". Scaling: " + scalingType + |
| ". Mirror: " + mirror); |
| this.layoutInPercentage.set(layoutInPercentage); |
| this.scalingType = scalingType; |
| this.mirror = mirror; |
| updateTextureProperties = true; |
| } |
| } |
| |
| private void setSize(final int videoWidth, final int videoHeight, final int rotation) { |
| if (videoWidth == this.videoWidth && videoHeight == this.videoHeight |
| && rotation == rotationDegree) { |
| return; |
| } |
| if (rendererEvents != null) { |
| Log.d(TAG, "ID: " + id + |
| ". Reporting frame resolution changed to " + videoWidth + " x " + videoHeight); |
| rendererEvents.onFrameResolutionChanged(videoWidth, videoHeight, rotation); |
| } |
| |
| // Frame re-allocation need to be synchronized with copying |
| // frame to textures in draw() function to avoid re-allocating |
| // the frame while it is being copied. |
| synchronized (frameToRenderQueue) { |
| Log.d(TAG, "ID: " + id + ". YuvImageRenderer.setSize: " + |
| videoWidth + " x " + videoHeight + " rotation " + rotation); |
| |
| this.videoWidth = videoWidth; |
| this.videoHeight = videoHeight; |
| rotationDegree = rotation; |
| int[] strides = { videoWidth, videoWidth / 2, videoWidth / 2 }; |
| |
| // Clear rendering queue. |
| frameToRenderQueue.poll(); |
| // Re-allocate / allocate the frame. |
| yuvFrameToRender = new I420Frame(videoWidth, videoHeight, rotationDegree, |
| strides, null, 0); |
| textureFrameToRender = new I420Frame(videoWidth, videoHeight, rotationDegree, |
| null, -1, 0); |
| updateTextureProperties = true; |
| Log.d(TAG, " YuvImageRenderer.setSize done."); |
| } |
| } |
| |
| @Override |
| public synchronized void renderFrame(I420Frame frame) { |
| if (surface == null) { |
| // This object has been released. |
| VideoRenderer.renderFrameDone(frame); |
| return; |
| } |
| if (!seenFrame && rendererEvents != null) { |
| Log.d(TAG, "ID: " + id + ". Reporting first rendered frame."); |
| rendererEvents.onFirstFrameRendered(); |
| } |
| setSize(frame.width, frame.height, frame.rotationDegree); |
| long now = System.nanoTime(); |
| framesReceived++; |
| synchronized (frameToRenderQueue) { |
| // Skip rendering of this frame if setSize() was not called. |
| if (yuvFrameToRender == null || textureFrameToRender == null) { |
| framesDropped++; |
| VideoRenderer.renderFrameDone(frame); |
| return; |
| } |
| // Check input frame parameters. |
| if (frame.yuvFrame) { |
| if (frame.yuvStrides[0] < frame.width || |
| frame.yuvStrides[1] < frame.width / 2 || |
| frame.yuvStrides[2] < frame.width / 2) { |
| Log.e(TAG, "Incorrect strides " + frame.yuvStrides[0] + ", " + |
| frame.yuvStrides[1] + ", " + frame.yuvStrides[2]); |
| VideoRenderer.renderFrameDone(frame); |
| return; |
| } |
| // Check incoming frame dimensions. |
| if (frame.width != yuvFrameToRender.width || |
| frame.height != yuvFrameToRender.height) { |
| throw new RuntimeException("Wrong frame size " + |
| frame.width + " x " + frame.height); |
| } |
| } |
| |
| if (frameToRenderQueue.size() > 0) { |
| // Skip rendering of this frame if previous frame was not rendered yet. |
| framesDropped++; |
| VideoRenderer.renderFrameDone(frame); |
| return; |
| } |
| |
| // Create a local copy of the frame. |
| if (frame.yuvFrame) { |
| yuvFrameToRender.copyFrom(frame); |
| rendererType = RendererType.RENDERER_YUV; |
| frameToRenderQueue.offer(yuvFrameToRender); |
| } else { |
| textureFrameToRender.copyFrom(frame); |
| rendererType = RendererType.RENDERER_TEXTURE; |
| frameToRenderQueue.offer(textureFrameToRender); |
| } |
| } |
| copyTimeNs += (System.nanoTime() - now); |
| seenFrame = true; |
| VideoRenderer.renderFrameDone(frame); |
| |
| // Request rendering. |
| surface.requestRender(); |
| } |
| } |
| |
| /** Passes GLSurfaceView to video renderer. */ |
| public static synchronized void setView(GLSurfaceView surface, |
| Runnable eglContextReadyCallback) { |
| Log.d(TAG, "VideoRendererGui.setView"); |
| instance = new VideoRendererGui(surface); |
| eglContextReady = eglContextReadyCallback; |
| } |
| |
| public static synchronized EGLContext getEGLContext() { |
| return eglContext; |
| } |
| |
| /** Releases GLSurfaceView video renderer. */ |
| public static synchronized void dispose() { |
| if (instance == null){ |
| return; |
| } |
| Log.d(TAG, "VideoRendererGui.dispose"); |
| synchronized (instance.yuvImageRenderers) { |
| for (YuvImageRenderer yuvImageRenderer : instance.yuvImageRenderers) { |
| yuvImageRenderer.release(); |
| } |
| instance.yuvImageRenderers.clear(); |
| } |
| instance.surface = null; |
| eglContext = null; |
| eglContextReady = null; |
| instance = null; |
| } |
| |
| /** |
| * Creates VideoRenderer with top left corner at (x, y) and resolution |
| * (width, height). All parameters are in percentage of screen resolution. |
| */ |
| public static VideoRenderer createGui(int x, int y, int width, int height, |
| RendererCommon.ScalingType scalingType, boolean mirror) throws Exception { |
| YuvImageRenderer javaGuiRenderer = create( |
| x, y, width, height, scalingType, mirror); |
| return new VideoRenderer(javaGuiRenderer); |
| } |
| |
| public static VideoRenderer.Callbacks createGuiRenderer( |
| int x, int y, int width, int height, |
| RendererCommon.ScalingType scalingType, boolean mirror) { |
| return create(x, y, width, height, scalingType, mirror); |
| } |
| |
| /** |
| * Creates VideoRenderer.Callbacks with top left corner at (x, y) and |
| * resolution (width, height). All parameters are in percentage of |
| * screen resolution. |
| */ |
| public static synchronized YuvImageRenderer create(int x, int y, int width, int height, |
| RendererCommon.ScalingType scalingType, boolean mirror) { |
| // Check display region parameters. |
| if (x < 0 || x > 100 || y < 0 || y > 100 || |
| width < 0 || width > 100 || height < 0 || height > 100 || |
| x + width > 100 || y + height > 100) { |
| throw new RuntimeException("Incorrect window parameters."); |
| } |
| |
| if (instance == null) { |
| throw new RuntimeException( |
| "Attempt to create yuv renderer before setting GLSurfaceView"); |
| } |
| final YuvImageRenderer yuvImageRenderer = new YuvImageRenderer( |
| instance.surface, instance.yuvImageRenderers.size(), |
| x, y, width, height, scalingType, mirror); |
| synchronized (instance.yuvImageRenderers) { |
| if (instance.onSurfaceCreatedCalled) { |
| // onSurfaceCreated has already been called for VideoRendererGui - |
| // need to create texture for new image and add image to the |
| // rendering list. |
| final CountDownLatch countDownLatch = new CountDownLatch(1); |
| instance.surface.queueEvent(new Runnable() { |
| public void run() { |
| yuvImageRenderer.createTextures(); |
| yuvImageRenderer.setScreenSize( |
| instance.screenWidth, instance.screenHeight); |
| countDownLatch.countDown(); |
| } |
| }); |
| // Wait for task completion. |
| try { |
| countDownLatch.await(); |
| } catch (InterruptedException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| // Add yuv renderer to rendering list. |
| instance.yuvImageRenderers.add(yuvImageRenderer); |
| } |
| return yuvImageRenderer; |
| } |
| |
| public static synchronized void update( |
| VideoRenderer.Callbacks renderer, int x, int y, int width, int height, |
| RendererCommon.ScalingType scalingType, boolean mirror) { |
| Log.d(TAG, "VideoRendererGui.update"); |
| if (instance == null) { |
| throw new RuntimeException( |
| "Attempt to update yuv renderer before setting GLSurfaceView"); |
| } |
| synchronized (instance.yuvImageRenderers) { |
| for (YuvImageRenderer yuvImageRenderer : instance.yuvImageRenderers) { |
| if (yuvImageRenderer == renderer) { |
| yuvImageRenderer.setPosition(x, y, width, height, scalingType, mirror); |
| } |
| } |
| } |
| } |
| |
| public static synchronized void setRendererEvents( |
| VideoRenderer.Callbacks renderer, RendererCommon.RendererEvents rendererEvents) { |
| Log.d(TAG, "VideoRendererGui.setRendererEvents"); |
| if (instance == null) { |
| throw new RuntimeException( |
| "Attempt to set renderer events before setting GLSurfaceView"); |
| } |
| synchronized (instance.yuvImageRenderers) { |
| for (YuvImageRenderer yuvImageRenderer : instance.yuvImageRenderers) { |
| if (yuvImageRenderer == renderer) { |
| yuvImageRenderer.rendererEvents = rendererEvents; |
| } |
| } |
| } |
| } |
| |
| public static synchronized void remove(VideoRenderer.Callbacks renderer) { |
| Log.d(TAG, "VideoRendererGui.remove"); |
| if (instance == null) { |
| throw new RuntimeException( |
| "Attempt to remove yuv renderer before setting GLSurfaceView"); |
| } |
| synchronized (instance.yuvImageRenderers) { |
| final int index = instance.yuvImageRenderers.indexOf(renderer); |
| if (index == -1) { |
| Log.w(TAG, "Couldn't remove renderer (not present in current list)"); |
| } else { |
| instance.yuvImageRenderers.remove(index).release(); |
| } |
| } |
| } |
| |
| @SuppressLint("NewApi") |
| @Override |
| public void onSurfaceCreated(GL10 unused, EGLConfig config) { |
| Log.d(TAG, "VideoRendererGui.onSurfaceCreated"); |
| // Store render EGL context. |
| if (CURRENT_SDK_VERSION >= EGL14_SDK_VERSION) { |
| synchronized (VideoRendererGui.class) { |
| eglContext = EGL14.eglGetCurrentContext(); |
| Log.d(TAG, "VideoRendererGui EGL Context: " + eglContext); |
| } |
| } |
| |
| synchronized (yuvImageRenderers) { |
| // Create drawer for YUV/OES frames. |
| drawer = new GlRectDrawer(); |
| // Create textures for all images. |
| for (YuvImageRenderer yuvImageRenderer : yuvImageRenderers) { |
| yuvImageRenderer.createTextures(); |
| } |
| onSurfaceCreatedCalled = true; |
| } |
| GlUtil.checkNoGLES2Error("onSurfaceCreated done"); |
| GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1); |
| GLES20.glClearColor(0.15f, 0.15f, 0.15f, 1.0f); |
| |
| // Fire EGL context ready event. |
| synchronized (VideoRendererGui.class) { |
| if (eglContextReady != null) { |
| eglContextReady.run(); |
| } |
| } |
| } |
| |
| @Override |
| public void onSurfaceChanged(GL10 unused, int width, int height) { |
| Log.d(TAG, "VideoRendererGui.onSurfaceChanged: " + |
| width + " x " + height + " "); |
| screenWidth = width; |
| screenHeight = height; |
| synchronized (yuvImageRenderers) { |
| for (YuvImageRenderer yuvImageRenderer : yuvImageRenderers) { |
| yuvImageRenderer.setScreenSize(screenWidth, screenHeight); |
| } |
| } |
| } |
| |
| @Override |
| public void onDrawFrame(GL10 unused) { |
| GLES20.glViewport(0, 0, screenWidth, screenHeight); |
| GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); |
| synchronized (yuvImageRenderers) { |
| for (YuvImageRenderer yuvImageRenderer : yuvImageRenderers) { |
| yuvImageRenderer.draw(drawer); |
| } |
| } |
| } |
| |
| } |