kopia lustrzana https://github.com/ryukoposting/Signal-Android
304 wiersze
11 KiB
Java
304 wiersze
11 KiB
Java
/*
|
|
* Copyright (C) 2013 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*
|
|
* This file has been modified by Signal.
|
|
*/
|
|
|
|
package org.thoughtcrime.securesms.video.videoconverter;
|
|
|
|
import android.graphics.SurfaceTexture;
|
|
import android.opengl.EGL14;
|
|
import android.view.Surface;
|
|
|
|
import org.signal.core.util.logging.Log;
|
|
|
|
import javax.microedition.khronos.egl.EGL10;
|
|
import javax.microedition.khronos.egl.EGLConfig;
|
|
import javax.microedition.khronos.egl.EGLContext;
|
|
import javax.microedition.khronos.egl.EGLDisplay;
|
|
import javax.microedition.khronos.egl.EGLSurface;
|
|
|
|
/**
|
|
* Holds state associated with a Surface used for MediaCodec decoder output.
|
|
* <p>
|
|
* The (width,height) constructor for this class will prepare GL, create a SurfaceTexture,
|
|
* and then create a Surface for that SurfaceTexture. The Surface can be passed to
|
|
* MediaCodec.configure() to receive decoder output. When a frame arrives, we latch the
|
|
* texture with updateTexImage, then render the texture with GL to a pbuffer.
|
|
* <p>
|
|
* The no-arg constructor skips the GL preparation step and doesn't allocate a pbuffer.
|
|
* Instead, it just creates the Surface and SurfaceTexture, and when a frame arrives
|
|
* we just draw it on whatever surface is current.
|
|
* <p>
|
|
* By default, the Surface will be using a BufferQueue in asynchronous mode, so we
|
|
* can potentially drop frames.
|
|
*/
|
|
final class OutputSurface implements SurfaceTexture.OnFrameAvailableListener {
|
|
private static final String TAG = "OutputSurface";
|
|
private static final boolean VERBOSE = false;
|
|
|
|
private static final int EGL_OPENGL_ES2_BIT = 4;
|
|
|
|
private EGL10 mEGL;
|
|
private EGLDisplay mEGLDisplay;
|
|
private EGLContext mEGLContext;
|
|
private EGLSurface mEGLSurface;
|
|
|
|
private SurfaceTexture mSurfaceTexture;
|
|
private Surface mSurface;
|
|
|
|
private final Object mFrameSyncObject = new Object(); // guards mFrameAvailable
|
|
private boolean mFrameAvailable;
|
|
|
|
private TextureRender mTextureRender;
|
|
|
|
/**
|
|
* Creates an OutputSurface backed by a pbuffer with the specifed dimensions. The new
|
|
* EGL context and surface will be made current. Creates a Surface that can be passed
|
|
* to MediaCodec.configure().
|
|
*/
|
|
OutputSurface(int width, int height, boolean flipX) throws TranscodingException {
|
|
if (width <= 0 || height <= 0) {
|
|
throw new IllegalArgumentException();
|
|
}
|
|
|
|
eglSetup(width, height);
|
|
makeCurrent();
|
|
|
|
setup(flipX);
|
|
}
|
|
|
|
/**
|
|
* Creates an OutputSurface using the current EGL context. Creates a Surface that can be
|
|
* passed to MediaCodec.configure().
|
|
*/
|
|
OutputSurface() throws TranscodingException {
|
|
setup(false);
|
|
}
|
|
|
|
/**
|
|
* Creates instances of TextureRender and SurfaceTexture, and a Surface associated
|
|
* with the SurfaceTexture.
|
|
*/
|
|
private void setup(boolean flipX) throws TranscodingException {
|
|
mTextureRender = new TextureRender(flipX);
|
|
mTextureRender.surfaceCreated();
|
|
|
|
// Even if we don't access the SurfaceTexture after the constructor returns, we
|
|
// still need to keep a reference to it. The Surface doesn't retain a reference
|
|
// at the Java level, so if we don't either then the object can get GCed, which
|
|
// causes the native finalizer to run.
|
|
if (VERBOSE) Log.d(TAG, "textureID=" + mTextureRender.getTextureId());
|
|
mSurfaceTexture = new SurfaceTexture(mTextureRender.getTextureId());
|
|
|
|
// This doesn't work if OutputSurface is created on the thread that CTS started for
|
|
// these test cases.
|
|
//
|
|
// The CTS-created thread has a Looper, and the SurfaceTexture constructor will
|
|
// create a Handler that uses it. The "frame available" message is delivered
|
|
// there, but since we're not a Looper-based thread we'll never see it. For
|
|
// this to do anything useful, OutputSurface must be created on a thread without
|
|
// a Looper, so that SurfaceTexture uses the main application Looper instead.
|
|
//
|
|
// Java language note: passing "this" out of a constructor is generally unwise,
|
|
// but we should be able to get away with it here.
|
|
mSurfaceTexture.setOnFrameAvailableListener(this);
|
|
|
|
mSurface = new Surface(mSurfaceTexture);
|
|
}
|
|
|
|
/**
|
|
* Prepares EGL. We want a GLES 2.0 context and a surface that supports pbuffer.
|
|
*/
|
|
private void eglSetup(int width, int height) throws TranscodingException {
|
|
mEGL = (EGL10)EGLContext.getEGL();
|
|
mEGLDisplay = mEGL.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
|
|
if (!mEGL.eglInitialize(mEGLDisplay, null)) {
|
|
throw new TranscodingException("unable to initialize EGL10");
|
|
}
|
|
|
|
// Configure EGL for pbuffer and OpenGL ES 2.0. We want enough RGB bits
|
|
// to be able to tell if the frame is reasonable.
|
|
int[] attribList = {
|
|
EGL10.EGL_RED_SIZE, 8,
|
|
EGL10.EGL_GREEN_SIZE, 8,
|
|
EGL10.EGL_BLUE_SIZE, 8,
|
|
EGL10.EGL_SURFACE_TYPE, EGL10.EGL_PBUFFER_BIT,
|
|
EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
|
|
EGL10.EGL_NONE
|
|
};
|
|
EGLConfig[] configs = new EGLConfig[1];
|
|
int[] numConfigs = new int[1];
|
|
if (!mEGL.eglChooseConfig(mEGLDisplay, attribList, configs, 1, numConfigs)) {
|
|
throw new TranscodingException("unable to find RGB888+pbuffer EGL config");
|
|
}
|
|
|
|
// Configure context for OpenGL ES 2.0.
|
|
int[] attrib_list = {
|
|
EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
|
|
EGL10.EGL_NONE
|
|
};
|
|
mEGLContext = mEGL.eglCreateContext(mEGLDisplay, configs[0], EGL10.EGL_NO_CONTEXT,
|
|
attrib_list);
|
|
checkEglError("eglCreateContext");
|
|
if (mEGLContext == null) {
|
|
throw new TranscodingException("null context");
|
|
}
|
|
|
|
// Create a pbuffer surface. By using this for output, we can use glReadPixels
|
|
// to test values in the output.
|
|
int[] surfaceAttribs = {
|
|
EGL10.EGL_WIDTH, width,
|
|
EGL10.EGL_HEIGHT, height,
|
|
EGL10.EGL_NONE
|
|
};
|
|
mEGLSurface = mEGL.eglCreatePbufferSurface(mEGLDisplay, configs[0], surfaceAttribs);
|
|
checkEglError("eglCreatePbufferSurface");
|
|
if (mEGLSurface == null) {
|
|
throw new TranscodingException("surface was null");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Discard all resources held by this class, notably the EGL context.
|
|
*/
|
|
public void release() {
|
|
if (mEGL != null) {
|
|
if (mEGL.eglGetCurrentContext().equals(mEGLContext)) {
|
|
// Clear the current context and surface to ensure they are discarded immediately.
|
|
mEGL.eglMakeCurrent(mEGLDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE,
|
|
EGL10.EGL_NO_CONTEXT);
|
|
}
|
|
mEGL.eglDestroySurface(mEGLDisplay, mEGLSurface);
|
|
mEGL.eglDestroyContext(mEGLDisplay, mEGLContext);
|
|
//mEGL.eglTerminate(mEGLDisplay);
|
|
}
|
|
|
|
mSurface.release();
|
|
|
|
// this causes a bunch of warnings that appear harmless but might confuse someone:
|
|
// W BufferQueue: [unnamed-3997-2] cancelBuffer: BufferQueue has been abandoned!
|
|
//mSurfaceTexture.release();
|
|
|
|
// null everything out so future attempts to use this object will cause an NPE
|
|
mEGLDisplay = null;
|
|
mEGLContext = null;
|
|
mEGLSurface = null;
|
|
mEGL = null;
|
|
|
|
mTextureRender = null;
|
|
mSurface = null;
|
|
mSurfaceTexture = null;
|
|
}
|
|
|
|
/**
|
|
* Makes our EGL context and surface current.
|
|
*/
|
|
private void makeCurrent() throws TranscodingException {
|
|
if (mEGL == null) {
|
|
throw new TranscodingException("not configured for makeCurrent");
|
|
}
|
|
checkEglError("before makeCurrent");
|
|
if (!mEGL.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)) {
|
|
throw new TranscodingException("eglMakeCurrent failed");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the Surface that we draw onto.
|
|
*/
|
|
public Surface getSurface() {
|
|
return mSurface;
|
|
}
|
|
|
|
/**
|
|
* Replaces the fragment shader.
|
|
*/
|
|
void changeFragmentShader(String fragmentShader) throws TranscodingException {
|
|
mTextureRender.changeFragmentShader(fragmentShader);
|
|
}
|
|
|
|
/**
|
|
* Latches the next buffer into the texture. Must be called from the thread that created
|
|
* the OutputSurface object, after the onFrameAvailable callback has signaled that new
|
|
* data is available.
|
|
*/
|
|
void awaitNewImage() throws TranscodingException {
|
|
final int TIMEOUT_MS = 750;
|
|
|
|
synchronized (mFrameSyncObject) {
|
|
final long expireTime = System.currentTimeMillis() + TIMEOUT_MS;
|
|
|
|
while (!mFrameAvailable) {
|
|
try {
|
|
// Wait for onFrameAvailable() to signal us. Use a timeout to avoid
|
|
// stalling the test if it doesn't arrive.
|
|
mFrameSyncObject.wait(TIMEOUT_MS);
|
|
|
|
if (!mFrameAvailable && System.currentTimeMillis() > expireTime) {
|
|
throw new TranscodingException("Surface frame wait timed out");
|
|
}
|
|
} catch (InterruptedException ie) {
|
|
// shouldn't happen
|
|
throw new TranscodingException(ie);
|
|
}
|
|
}
|
|
mFrameAvailable = false;
|
|
}
|
|
|
|
// Latch the data.
|
|
TextureRender.checkGlError("before updateTexImage");
|
|
mSurfaceTexture.updateTexImage();
|
|
}
|
|
|
|
/**
|
|
* Draws the data from SurfaceTexture onto the current EGL surface.
|
|
*/
|
|
void drawImage() throws TranscodingException {
|
|
mTextureRender.drawFrame(mSurfaceTexture);
|
|
}
|
|
|
|
@Override
|
|
public void onFrameAvailable(SurfaceTexture st) {
|
|
if (VERBOSE) Log.d(TAG, "new frame available");
|
|
synchronized (mFrameSyncObject) {
|
|
if (mFrameAvailable) {
|
|
try {
|
|
throw new TranscodingException("mFrameAvailable already set, frame could be dropped");
|
|
} catch (TranscodingException e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
mFrameAvailable = true;
|
|
mFrameSyncObject.notifyAll();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks for EGL errors.
|
|
*/
|
|
private void checkEglError(String msg) throws TranscodingException {
|
|
boolean failed = false;
|
|
int error;
|
|
while ((error = mEGL.eglGetError()) != EGL10.EGL_SUCCESS) {
|
|
Log.e(TAG, msg + ": EGL error: 0x" + Integer.toHexString(error));
|
|
failed = true;
|
|
}
|
|
if (failed) {
|
|
throw new TranscodingException("EGL error encountered (see log)");
|
|
}
|
|
}
|
|
}
|