Reduce frame timeout crashes.

This commit is contained in:
Alan Evans 2019-08-02 15:57:20 -04:00 committed by GitHub
parent 19b2658414
commit dcc147d994
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 97 additions and 71 deletions

View File

@ -22,7 +22,7 @@ import org.thoughtcrime.securesms.util.BitmapDecodingException;
import org.thoughtcrime.securesms.util.BitmapUtil; import org.thoughtcrime.securesms.util.BitmapUtil;
import org.thoughtcrime.securesms.util.MediaUtil; import org.thoughtcrime.securesms.util.MediaUtil;
import org.thoughtcrime.securesms.video.InMemoryTranscoder; import org.thoughtcrime.securesms.video.InMemoryTranscoder;
import org.thoughtcrime.securesms.video.videoconverter.BadVideoException; import org.thoughtcrime.securesms.video.videoconverter.EncodingException;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
@ -114,7 +114,7 @@ final class MediaResizer {
} }
} }
} }
} catch (IOException | MmsException | BadVideoException e) { } catch (IOException | MmsException | EncodingException e) {
throw new UndeliverableMessageException("Failed to transcode", e); throw new UndeliverableMessageException("Failed to transcode", e);
} }
} }

View File

@ -14,7 +14,7 @@ import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.mms.MediaStream; import org.thoughtcrime.securesms.mms.MediaStream;
import org.thoughtcrime.securesms.transport.UndeliverableMessageException; import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
import org.thoughtcrime.securesms.util.MemoryFileDescriptor; import org.thoughtcrime.securesms.util.MemoryFileDescriptor;
import org.thoughtcrime.securesms.video.videoconverter.BadVideoException; import org.thoughtcrime.securesms.video.videoconverter.EncodingException;
import org.thoughtcrime.securesms.video.videoconverter.MediaConverter; import org.thoughtcrime.securesms.video.videoconverter.MediaConverter;
import java.io.Closeable; import java.io.Closeable;
@ -80,7 +80,7 @@ public final class InMemoryTranscoder implements Closeable {
: OUTPUT_FORMAT; : OUTPUT_FORMAT;
} }
public @NonNull MediaStream transcode(@NonNull Progress progress) throws IOException, UndeliverableMessageException, BadVideoException { public @NonNull MediaStream transcode(@NonNull Progress progress) throws IOException, UndeliverableMessageException, EncodingException {
if (memoryFile != null) throw new AssertionError("Not expecting to reuse transcoder"); if (memoryFile != null) throw new AssertionError("Not expecting to reuse transcoder");
float durationSec = duration / 1000f; float durationSec = duration / 1000f;

View File

@ -1,6 +0,0 @@
package org.thoughtcrime.securesms.video.videoconverter;
public final class BadVideoException extends Exception {
BadVideoException() {
}
}

View File

@ -0,0 +1,11 @@
package org.thoughtcrime.securesms.video.videoconverter;
public final class EncodingException extends Exception {
EncodingException(String message) {
super(message);
}
EncodingException(String message, Exception inner) {
super(message, inner);
}
}

View File

@ -52,7 +52,7 @@ final class InputSurface {
/** /**
* Creates an InputSurface from a Surface. * Creates an InputSurface from a Surface.
*/ */
public InputSurface(Surface surface) { InputSurface(Surface surface) throws TranscodingException {
if (surface == null) { if (surface == null) {
throw new NullPointerException(); throw new NullPointerException();
} }
@ -64,15 +64,15 @@ final class InputSurface {
/** /**
* Prepares EGL. We want a GLES 2.0 context and a surface that supports recording. * Prepares EGL. We want a GLES 2.0 context and a surface that supports recording.
*/ */
private void eglSetup() { private void eglSetup() throws TranscodingException {
mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY); mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) { if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) {
throw new RuntimeException("unable to get EGL14 display"); throw new TranscodingException("unable to get EGL14 display");
} }
int[] version = new int[2]; int[] version = new int[2];
if (!EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1)) { if (!EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1)) {
mEGLDisplay = null; mEGLDisplay = null;
throw new RuntimeException("unable to initialize EGL14"); throw new TranscodingException("unable to initialize EGL14");
} }
// Configure EGL for pbuffer and OpenGL ES 2.0. We want enough RGB bits // Configure EGL for pbuffer and OpenGL ES 2.0. We want enough RGB bits
@ -89,7 +89,7 @@ final class InputSurface {
int[] numConfigs = new int[1]; int[] numConfigs = new int[1];
if (!EGL14.eglChooseConfig(mEGLDisplay, attribList, 0, configs, 0, configs.length, if (!EGL14.eglChooseConfig(mEGLDisplay, attribList, 0, configs, 0, configs.length,
numConfigs, 0)) { numConfigs, 0)) {
throw new RuntimeException("unable to find RGB888+recordable ES2 EGL config"); throw new TranscodingException("unable to find RGB888+recordable ES2 EGL config");
} }
// Configure context for OpenGL ES 2.0. // Configure context for OpenGL ES 2.0.
@ -101,7 +101,7 @@ final class InputSurface {
attrib_list, 0); attrib_list, 0);
checkEglError("eglCreateContext"); checkEglError("eglCreateContext");
if (mEGLContext == null) { if (mEGLContext == null) {
throw new RuntimeException("null context"); throw new TranscodingException("null context");
} }
// Create a window surface, and attach it to the Surface we received. // Create a window surface, and attach it to the Surface we received.
@ -112,7 +112,7 @@ final class InputSurface {
surfaceAttribs, 0); surfaceAttribs, 0);
checkEglError("eglCreateWindowSurface"); checkEglError("eglCreateWindowSurface");
if (mEGLSurface == null) { if (mEGLSurface == null) {
throw new RuntimeException("surface was null"); throw new TranscodingException("surface was null");
} }
} }
@ -143,16 +143,16 @@ final class InputSurface {
/** /**
* Makes our EGL context and surface current. * Makes our EGL context and surface current.
*/ */
public void makeCurrent() { void makeCurrent() throws TranscodingException {
if (!EGL14.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)) { if (!EGL14.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)) {
throw new RuntimeException("eglMakeCurrent failed"); throw new TranscodingException("eglMakeCurrent failed");
} }
} }
/** /**
* Calls eglSwapBuffers. Use this to "publish" the current frame. * Calls eglSwapBuffers. Use this to "publish" the current frame.
*/ */
public boolean swapBuffers() { boolean swapBuffers() {
return EGL14.eglSwapBuffers(mEGLDisplay, mEGLSurface); return EGL14.eglSwapBuffers(mEGLDisplay, mEGLSurface);
} }
@ -166,14 +166,14 @@ final class InputSurface {
/** /**
* Sends the presentation time stamp to EGL. Time is expressed in nanoseconds. * Sends the presentation time stamp to EGL. Time is expressed in nanoseconds.
*/ */
public void setPresentationTime(long nsecs) { void setPresentationTime(long nsecs) {
EGLExt.eglPresentationTimeANDROID(mEGLDisplay, mEGLSurface, nsecs); EGLExt.eglPresentationTimeANDROID(mEGLDisplay, mEGLSurface, nsecs);
} }
/** /**
* Checks for EGL errors. * Checks for EGL errors.
*/ */
private void checkEglError(String msg) { private static void checkEglError(String msg) throws TranscodingException {
boolean failed = false; boolean failed = false;
int error; int error;
while ((error = EGL14.eglGetError()) != EGL14.EGL_SUCCESS) { while ((error = EGL14.eglGetError()) != EGL14.EGL_SUCCESS) {
@ -181,7 +181,7 @@ final class InputSurface {
failed = true; failed = true;
} }
if (failed) { if (failed) {
throw new RuntimeException("EGL error encountered (see log)"); throw new TranscodingException("EGL error encountered (see log)");
} }
} }
} }

View File

@ -139,7 +139,7 @@ public final class MediaConverter {
} }
@WorkerThread @WorkerThread
public void convert() throws BadVideoException, IOException { public void convert() throws EncodingException, IOException {
// Exception that may be thrown during release. // Exception that may be thrown during release.
Exception exception = null; Exception exception = null;
Muxer muxer = null; Muxer muxer = null;
@ -151,8 +151,7 @@ public final class MediaConverter {
audioTrackConverter = AudioTrackConverter.create(mInput, mTimeFrom, mTimeTo, mAudioBitrate); audioTrackConverter = AudioTrackConverter.create(mInput, mTimeFrom, mTimeTo, mAudioBitrate);
if (videoTrackConverter == null && audioTrackConverter == null) { if (videoTrackConverter == null && audioTrackConverter == null) {
Log.e(TAG, "no video and audio tracks"); throw new EncodingException("No video and audio tracks");
throw new BadVideoException();
} }
muxer = mOutput.createMuxer(); muxer = mOutput.createMuxer();
@ -162,7 +161,7 @@ public final class MediaConverter {
audioTrackConverter, audioTrackConverter,
muxer); muxer);
} catch (BadVideoException | IOException e) { } catch (EncodingException | IOException e) {
Log.e(TAG, "error converting", e); Log.e(TAG, "error converting", e);
exception = e; exception = e;
throw e; throw e;
@ -207,7 +206,7 @@ public final class MediaConverter {
} }
} }
if (exception != null) { if (exception != null) {
throw new RuntimeException(exception); throw new EncodingException("Transcode failed", exception);
} }
} }
@ -217,7 +216,7 @@ public final class MediaConverter {
private void doExtractDecodeEditEncodeMux( private void doExtractDecodeEditEncodeMux(
final @Nullable VideoTrackConverter videoTrackConverter, final @Nullable VideoTrackConverter videoTrackConverter,
final @Nullable AudioTrackConverter audioTrackConverter, final @Nullable AudioTrackConverter audioTrackConverter,
final @NonNull Muxer muxer) throws IOException { final @NonNull Muxer muxer) throws IOException, TranscodingException {
boolean muxing = false; boolean muxing = false;
int percentProcessed = 0; int percentProcessed = 0;

View File

@ -69,7 +69,7 @@ final class OutputSurface implements SurfaceTexture.OnFrameAvailableListener {
* EGL context and surface will be made current. Creates a Surface that can be passed * EGL context and surface will be made current. Creates a Surface that can be passed
* to MediaCodec.configure(). * to MediaCodec.configure().
*/ */
public OutputSurface(int width, int height) { OutputSurface(int width, int height) throws TranscodingException {
if (width <= 0 || height <= 0) { if (width <= 0 || height <= 0) {
throw new IllegalArgumentException(); throw new IllegalArgumentException();
} }
@ -84,7 +84,7 @@ final class OutputSurface implements SurfaceTexture.OnFrameAvailableListener {
* Creates an OutputSurface using the current EGL context. Creates a Surface that can be * Creates an OutputSurface using the current EGL context. Creates a Surface that can be
* passed to MediaCodec.configure(). * passed to MediaCodec.configure().
*/ */
public OutputSurface() { OutputSurface() throws TranscodingException {
setup(); setup();
} }
@ -92,7 +92,7 @@ final class OutputSurface implements SurfaceTexture.OnFrameAvailableListener {
* Creates instances of TextureRender and SurfaceTexture, and a Surface associated * Creates instances of TextureRender and SurfaceTexture, and a Surface associated
* with the SurfaceTexture. * with the SurfaceTexture.
*/ */
private void setup() { private void setup() throws TranscodingException {
mTextureRender = new TextureRender(); mTextureRender = new TextureRender();
mTextureRender.surfaceCreated(); mTextureRender.surfaceCreated();
@ -122,11 +122,11 @@ final class OutputSurface implements SurfaceTexture.OnFrameAvailableListener {
/** /**
* Prepares EGL. We want a GLES 2.0 context and a surface that supports pbuffer. * Prepares EGL. We want a GLES 2.0 context and a surface that supports pbuffer.
*/ */
private void eglSetup(int width, int height) { private void eglSetup(int width, int height) throws TranscodingException {
mEGL = (EGL10)EGLContext.getEGL(); mEGL = (EGL10)EGLContext.getEGL();
mEGLDisplay = mEGL.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); mEGLDisplay = mEGL.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
if (!mEGL.eglInitialize(mEGLDisplay, null)) { if (!mEGL.eglInitialize(mEGLDisplay, null)) {
throw new RuntimeException("unable to initialize EGL10"); throw new TranscodingException("unable to initialize EGL10");
} }
// Configure EGL for pbuffer and OpenGL ES 2.0. We want enough RGB bits // Configure EGL for pbuffer and OpenGL ES 2.0. We want enough RGB bits
@ -142,7 +142,7 @@ final class OutputSurface implements SurfaceTexture.OnFrameAvailableListener {
EGLConfig[] configs = new EGLConfig[1]; EGLConfig[] configs = new EGLConfig[1];
int[] numConfigs = new int[1]; int[] numConfigs = new int[1];
if (!mEGL.eglChooseConfig(mEGLDisplay, attribList, configs, 1, numConfigs)) { if (!mEGL.eglChooseConfig(mEGLDisplay, attribList, configs, 1, numConfigs)) {
throw new RuntimeException("unable to find RGB888+pbuffer EGL config"); throw new TranscodingException("unable to find RGB888+pbuffer EGL config");
} }
// Configure context for OpenGL ES 2.0. // Configure context for OpenGL ES 2.0.
@ -154,7 +154,7 @@ final class OutputSurface implements SurfaceTexture.OnFrameAvailableListener {
attrib_list); attrib_list);
checkEglError("eglCreateContext"); checkEglError("eglCreateContext");
if (mEGLContext == null) { if (mEGLContext == null) {
throw new RuntimeException("null context"); throw new TranscodingException("null context");
} }
// Create a pbuffer surface. By using this for output, we can use glReadPixels // Create a pbuffer surface. By using this for output, we can use glReadPixels
@ -167,7 +167,7 @@ final class OutputSurface implements SurfaceTexture.OnFrameAvailableListener {
mEGLSurface = mEGL.eglCreatePbufferSurface(mEGLDisplay, configs[0], surfaceAttribs); mEGLSurface = mEGL.eglCreatePbufferSurface(mEGLDisplay, configs[0], surfaceAttribs);
checkEglError("eglCreatePbufferSurface"); checkEglError("eglCreatePbufferSurface");
if (mEGLSurface == null) { if (mEGLSurface == null) {
throw new RuntimeException("surface was null"); throw new TranscodingException("surface was null");
} }
} }
@ -206,13 +206,13 @@ final class OutputSurface implements SurfaceTexture.OnFrameAvailableListener {
/** /**
* Makes our EGL context and surface current. * Makes our EGL context and surface current.
*/ */
public void makeCurrent() { private void makeCurrent() throws TranscodingException {
if (mEGL == null) { if (mEGL == null) {
throw new RuntimeException("not configured for makeCurrent"); throw new TranscodingException("not configured for makeCurrent");
} }
checkEglError("before makeCurrent"); checkEglError("before makeCurrent");
if (!mEGL.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)) { if (!mEGL.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)) {
throw new RuntimeException("eglMakeCurrent failed"); throw new TranscodingException("eglMakeCurrent failed");
} }
} }
@ -226,7 +226,7 @@ final class OutputSurface implements SurfaceTexture.OnFrameAvailableListener {
/** /**
* Replaces the fragment shader. * Replaces the fragment shader.
*/ */
public void changeFragmentShader(String fragmentShader) { void changeFragmentShader(String fragmentShader) throws TranscodingException {
mTextureRender.changeFragmentShader(fragmentShader); mTextureRender.changeFragmentShader(fragmentShader);
} }
@ -235,36 +235,38 @@ final class OutputSurface implements SurfaceTexture.OnFrameAvailableListener {
* the OutputSurface object, after the onFrameAvailable callback has signaled that new * the OutputSurface object, after the onFrameAvailable callback has signaled that new
* data is available. * data is available.
*/ */
public void awaitNewImage() { void awaitNewImage() throws TranscodingException {
final int TIMEOUT_MS = 500; final int TIMEOUT_MS = 750;
synchronized (mFrameSyncObject) { synchronized (mFrameSyncObject) {
final long expireTime = System.currentTimeMillis() + TIMEOUT_MS;
while (!mFrameAvailable) { while (!mFrameAvailable) {
try { try {
// Wait for onFrameAvailable() to signal us. Use a timeout to avoid // Wait for onFrameAvailable() to signal us. Use a timeout to avoid
// stalling the test if it doesn't arrive. // stalling the test if it doesn't arrive.
mFrameSyncObject.wait(TIMEOUT_MS); mFrameSyncObject.wait(TIMEOUT_MS);
if (!mFrameAvailable) {
// TODO: if "spurious wakeup", continue while loop if (!mFrameAvailable && System.currentTimeMillis() > expireTime) {
throw new RuntimeException("Surface frame wait timed out"); throw new TranscodingException("Surface frame wait timed out");
} }
} catch (InterruptedException ie) { } catch (InterruptedException ie) {
// shouldn't happen // shouldn't happen
throw new RuntimeException(ie); throw new TranscodingException(ie);
} }
} }
mFrameAvailable = false; mFrameAvailable = false;
} }
// Latch the data. // Latch the data.
mTextureRender.checkGlError("before updateTexImage"); TextureRender.checkGlError("before updateTexImage");
mSurfaceTexture.updateTexImage(); mSurfaceTexture.updateTexImage();
} }
/** /**
* Draws the data from SurfaceTexture onto the current EGL surface. * Draws the data from SurfaceTexture onto the current EGL surface.
*/ */
public void drawImage() { void drawImage() throws TranscodingException {
mTextureRender.drawFrame(mSurfaceTexture); mTextureRender.drawFrame(mSurfaceTexture);
} }
@ -273,7 +275,11 @@ final class OutputSurface implements SurfaceTexture.OnFrameAvailableListener {
if (VERBOSE) Log.d(TAG, "new frame available"); if (VERBOSE) Log.d(TAG, "new frame available");
synchronized (mFrameSyncObject) { synchronized (mFrameSyncObject) {
if (mFrameAvailable) { if (mFrameAvailable) {
throw new RuntimeException("mFrameAvailable already set, frame could be dropped"); try {
throw new TranscodingException("mFrameAvailable already set, frame could be dropped");
} catch (TranscodingException e) {
e.printStackTrace();
}
} }
mFrameAvailable = true; mFrameAvailable = true;
mFrameSyncObject.notifyAll(); mFrameSyncObject.notifyAll();
@ -283,7 +289,7 @@ final class OutputSurface implements SurfaceTexture.OnFrameAvailableListener {
/** /**
* Checks for EGL errors. * Checks for EGL errors.
*/ */
private void checkEglError(String msg) { private void checkEglError(String msg) throws TranscodingException {
boolean failed = false; boolean failed = false;
int error; int error;
while ((error = mEGL.eglGetError()) != EGL10.EGL_SUCCESS) { while ((error = mEGL.eglGetError()) != EGL10.EGL_SUCCESS) {
@ -291,7 +297,7 @@ final class OutputSurface implements SurfaceTexture.OnFrameAvailableListener {
failed = true; failed = true;
} }
if (failed) { if (failed) {
throw new RuntimeException("EGL error encountered (see log)"); throw new TranscodingException("EGL error encountered (see log)");
} }
} }
} }

View File

@ -79,7 +79,7 @@ final class TextureRender {
private int maPositionHandle; private int maPositionHandle;
private int maTextureHandle; private int maTextureHandle;
public TextureRender() { TextureRender() {
mTriangleVertices = ByteBuffer.allocateDirect( mTriangleVertices = ByteBuffer.allocateDirect(
mTriangleVerticesData.length * FLOAT_SIZE_BYTES) mTriangleVerticesData.length * FLOAT_SIZE_BYTES)
.order(ByteOrder.nativeOrder()).asFloatBuffer(); .order(ByteOrder.nativeOrder()).asFloatBuffer();
@ -88,11 +88,11 @@ final class TextureRender {
Matrix.setIdentityM(mSTMatrix, 0); Matrix.setIdentityM(mSTMatrix, 0);
} }
public int getTextureId() { int getTextureId() {
return mTextureID; return mTextureID;
} }
public void drawFrame(SurfaceTexture st) { void drawFrame(SurfaceTexture st) throws TranscodingException {
checkGlError("onDrawFrame start"); checkGlError("onDrawFrame start");
st.getTransformMatrix(mSTMatrix); st.getTransformMatrix(mSTMatrix);
@ -131,32 +131,32 @@ final class TextureRender {
/** /**
* Initializes GL state. Call this after the EGL surface has been created and made current. * Initializes GL state. Call this after the EGL surface has been created and made current.
*/ */
public void surfaceCreated() { void surfaceCreated() throws TranscodingException {
mProgram = createProgram(VERTEX_SHADER, FRAGMENT_SHADER); mProgram = createProgram(VERTEX_SHADER, FRAGMENT_SHADER);
if (mProgram == 0) { if (mProgram == 0) {
throw new RuntimeException("failed creating program"); throw new TranscodingException("failed creating program");
} }
maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition"); maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");
checkGlError("glGetAttribLocation aPosition"); checkGlError("glGetAttribLocation aPosition");
if (maPositionHandle == -1) { if (maPositionHandle == -1) {
throw new RuntimeException("Could not get attrib location for aPosition"); throw new TranscodingException("Could not get attrib location for aPosition");
} }
maTextureHandle = GLES20.glGetAttribLocation(mProgram, "aTextureCoord"); maTextureHandle = GLES20.glGetAttribLocation(mProgram, "aTextureCoord");
checkGlError("glGetAttribLocation aTextureCoord"); checkGlError("glGetAttribLocation aTextureCoord");
if (maTextureHandle == -1) { if (maTextureHandle == -1) {
throw new RuntimeException("Could not get attrib location for aTextureCoord"); throw new TranscodingException("Could not get attrib location for aTextureCoord");
} }
muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix"); muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
checkGlError("glGetUniformLocation uMVPMatrix"); checkGlError("glGetUniformLocation uMVPMatrix");
if (muMVPMatrixHandle == -1) { if (muMVPMatrixHandle == -1) {
throw new RuntimeException("Could not get attrib location for uMVPMatrix"); throw new TranscodingException("Could not get attrib location for uMVPMatrix");
} }
muSTMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uSTMatrix"); muSTMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uSTMatrix");
checkGlError("glGetUniformLocation uSTMatrix"); checkGlError("glGetUniformLocation uSTMatrix");
if (muSTMatrixHandle == -1) { if (muSTMatrixHandle == -1) {
throw new RuntimeException("Could not get attrib location for uSTMatrix"); throw new TranscodingException("Could not get attrib location for uSTMatrix");
} }
int[] textures = new int[1]; int[] textures = new int[1];
@ -180,15 +180,15 @@ final class TextureRender {
/** /**
* Replaces the fragment shader. * Replaces the fragment shader.
*/ */
public void changeFragmentShader(String fragmentShader) { public void changeFragmentShader(String fragmentShader) throws TranscodingException {
GLES20.glDeleteProgram(mProgram); GLES20.glDeleteProgram(mProgram);
mProgram = createProgram(VERTEX_SHADER, fragmentShader); mProgram = createProgram(VERTEX_SHADER, fragmentShader);
if (mProgram == 0) { if (mProgram == 0) {
throw new RuntimeException("failed creating program"); throw new TranscodingException("failed creating program");
} }
} }
private int loadShader(int shaderType, String source) { private static int loadShader(int shaderType, String source) throws TranscodingException {
int shader = GLES20.glCreateShader(shaderType); int shader = GLES20.glCreateShader(shaderType);
checkGlError("glCreateShader type=" + shaderType); checkGlError("glCreateShader type=" + shaderType);
GLES20.glShaderSource(shader, source); GLES20.glShaderSource(shader, source);
@ -204,7 +204,7 @@ final class TextureRender {
return shader; return shader;
} }
private int createProgram(String vertexSource, String fragmentSource) { private int createProgram(String vertexSource, String fragmentSource) throws TranscodingException {
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource); int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
if (vertexShader == 0) { if (vertexShader == 0) {
return 0; return 0;
@ -235,11 +235,15 @@ final class TextureRender {
return program; return program;
} }
public void checkGlError(String op) { static void checkGlError(String msg) throws TranscodingException {
boolean failed = false;
int error; int error;
while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) { while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
Log.e(TAG, op + ": glError " + error); Log.e(TAG, msg + ": GLES20 error: 0x" + Integer.toHexString(error));
throw new RuntimeException(op + ": glError " + error); failed = true;
}
if (failed) {
throw new TranscodingException("GLES20 error encountered (see log)");
} }
} }
} }

View File

@ -0,0 +1,12 @@
package org.thoughtcrime.securesms.video.videoconverter;
final class TranscodingException extends Exception {
TranscodingException(String message) {
super(message);
}
TranscodingException(Throwable inner) {
super(inner);
}
}

View File

@ -67,7 +67,7 @@ final class VideoTrackConverter {
final long timeTo, final long timeTo,
final int videoResolution, final int videoResolution,
final int videoBitrate, final int videoBitrate,
final @NonNull String videoCodec) throws IOException { final @NonNull String videoCodec) throws IOException, TranscodingException {
final MediaExtractor videoExtractor = input.createExtractor(); final MediaExtractor videoExtractor = input.createExtractor();
final int videoInputTrack = getAndSelectVideoTrackIndex(videoExtractor); final int videoInputTrack = getAndSelectVideoTrackIndex(videoExtractor);
@ -85,7 +85,7 @@ final class VideoTrackConverter {
final long timeTo, final long timeTo,
final int videoResolution, final int videoResolution,
final int videoBitrate, final int videoBitrate,
final @NonNull String videoCodec) throws IOException { final @NonNull String videoCodec) throws IOException, TranscodingException {
mTimeFrom = timeFrom; mTimeFrom = timeFrom;
mTimeTo = timeTo; mTimeTo = timeTo;
@ -175,7 +175,7 @@ final class VideoTrackConverter {
} }
} }
void step() throws IOException { void step() throws IOException, TranscodingException {
// Extract video from file and feed to decoder. // Extract video from file and feed to decoder.
// Do not extract video if we have determined the output format but we are not yet // Do not extract video if we have determined the output format but we are not yet
// ready to mux the frames. // ready to mux the frames.