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.MediaUtil;
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.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);
}
}

View file

@ -14,7 +14,7 @@ import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.mms.MediaStream;
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
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 java.io.Closeable;
@ -80,7 +80,7 @@ public final class InMemoryTranscoder implements Closeable {
: 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");
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.
*/
public InputSurface(Surface surface) {
InputSurface(Surface surface) throws TranscodingException {
if (surface == null) {
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.
*/
private void eglSetup() {
private void eglSetup() throws TranscodingException {
mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_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];
if (!EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1)) {
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
@ -89,7 +89,7 @@ final class InputSurface {
int[] numConfigs = new int[1];
if (!EGL14.eglChooseConfig(mEGLDisplay, attribList, 0, configs, 0, configs.length,
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.
@ -101,7 +101,7 @@ final class InputSurface {
attrib_list, 0);
checkEglError("eglCreateContext");
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.
@ -112,7 +112,7 @@ final class InputSurface {
surfaceAttribs, 0);
checkEglError("eglCreateWindowSurface");
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.
*/
public void makeCurrent() {
void makeCurrent() throws TranscodingException {
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.
*/
public boolean swapBuffers() {
boolean swapBuffers() {
return EGL14.eglSwapBuffers(mEGLDisplay, mEGLSurface);
}
@ -166,14 +166,14 @@ final class InputSurface {
/**
* 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);
}
/**
* Checks for EGL errors.
*/
private void checkEglError(String msg) {
private static void checkEglError(String msg) throws TranscodingException {
boolean failed = false;
int error;
while ((error = EGL14.eglGetError()) != EGL14.EGL_SUCCESS) {
@ -181,7 +181,7 @@ final class InputSurface {
failed = true;
}
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
public void convert() throws BadVideoException, IOException {
public void convert() throws EncodingException, IOException {
// Exception that may be thrown during release.
Exception exception = null;
Muxer muxer = null;
@ -151,8 +151,7 @@ public final class MediaConverter {
audioTrackConverter = AudioTrackConverter.create(mInput, mTimeFrom, mTimeTo, mAudioBitrate);
if (videoTrackConverter == null && audioTrackConverter == null) {
Log.e(TAG, "no video and audio tracks");
throw new BadVideoException();
throw new EncodingException("No video and audio tracks");
}
muxer = mOutput.createMuxer();
@ -162,7 +161,7 @@ public final class MediaConverter {
audioTrackConverter,
muxer);
} catch (BadVideoException | IOException e) {
} catch (EncodingException | IOException e) {
Log.e(TAG, "error converting", e);
exception = e;
throw e;
@ -207,7 +206,7 @@ public final class MediaConverter {
}
}
if (exception != null) {
throw new RuntimeException(exception);
throw new EncodingException("Transcode failed", exception);
}
}
@ -217,7 +216,7 @@ public final class MediaConverter {
private void doExtractDecodeEditEncodeMux(
final @Nullable VideoTrackConverter videoTrackConverter,
final @Nullable AudioTrackConverter audioTrackConverter,
final @NonNull Muxer muxer) throws IOException {
final @NonNull Muxer muxer) throws IOException, TranscodingException {
boolean muxing = false;
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
* to MediaCodec.configure().
*/
public OutputSurface(int width, int height) {
OutputSurface(int width, int height) throws TranscodingException {
if (width <= 0 || height <= 0) {
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
* passed to MediaCodec.configure().
*/
public OutputSurface() {
OutputSurface() throws TranscodingException {
setup();
}
@ -92,7 +92,7 @@ final class OutputSurface implements SurfaceTexture.OnFrameAvailableListener {
* Creates instances of TextureRender and SurfaceTexture, and a Surface associated
* with the SurfaceTexture.
*/
private void setup() {
private void setup() throws TranscodingException {
mTextureRender = new TextureRender();
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.
*/
private void eglSetup(int width, int height) {
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 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
@ -142,7 +142,7 @@ final class OutputSurface implements SurfaceTexture.OnFrameAvailableListener {
EGLConfig[] configs = new EGLConfig[1];
int[] numConfigs = new int[1];
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.
@ -154,7 +154,7 @@ final class OutputSurface implements SurfaceTexture.OnFrameAvailableListener {
attrib_list);
checkEglError("eglCreateContext");
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
@ -167,7 +167,7 @@ final class OutputSurface implements SurfaceTexture.OnFrameAvailableListener {
mEGLSurface = mEGL.eglCreatePbufferSurface(mEGLDisplay, configs[0], surfaceAttribs);
checkEglError("eglCreatePbufferSurface");
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.
*/
public void makeCurrent() {
private void makeCurrent() throws TranscodingException {
if (mEGL == null) {
throw new RuntimeException("not configured for makeCurrent");
throw new TranscodingException("not configured for makeCurrent");
}
checkEglError("before makeCurrent");
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.
*/
public void changeFragmentShader(String fragmentShader) {
void changeFragmentShader(String fragmentShader) throws TranscodingException {
mTextureRender.changeFragmentShader(fragmentShader);
}
@ -235,36 +235,38 @@ final class OutputSurface implements SurfaceTexture.OnFrameAvailableListener {
* the OutputSurface object, after the onFrameAvailable callback has signaled that new
* data is available.
*/
public void awaitNewImage() {
final int TIMEOUT_MS = 500;
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) {
// TODO: if "spurious wakeup", continue while loop
throw new RuntimeException("Surface frame wait timed out");
if (!mFrameAvailable && System.currentTimeMillis() > expireTime) {
throw new TranscodingException("Surface frame wait timed out");
}
} catch (InterruptedException ie) {
// shouldn't happen
throw new RuntimeException(ie);
throw new TranscodingException(ie);
}
}
mFrameAvailable = false;
}
// Latch the data.
mTextureRender.checkGlError("before updateTexImage");
TextureRender.checkGlError("before updateTexImage");
mSurfaceTexture.updateTexImage();
}
/**
* Draws the data from SurfaceTexture onto the current EGL surface.
*/
public void drawImage() {
void drawImage() throws TranscodingException {
mTextureRender.drawFrame(mSurfaceTexture);
}
@ -273,7 +275,11 @@ final class OutputSurface implements SurfaceTexture.OnFrameAvailableListener {
if (VERBOSE) Log.d(TAG, "new frame available");
synchronized (mFrameSyncObject) {
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;
mFrameSyncObject.notifyAll();
@ -283,7 +289,7 @@ final class OutputSurface implements SurfaceTexture.OnFrameAvailableListener {
/**
* Checks for EGL errors.
*/
private void checkEglError(String msg) {
private void checkEglError(String msg) throws TranscodingException {
boolean failed = false;
int error;
while ((error = mEGL.eglGetError()) != EGL10.EGL_SUCCESS) {
@ -291,7 +297,7 @@ final class OutputSurface implements SurfaceTexture.OnFrameAvailableListener {
failed = true;
}
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 maTextureHandle;
public TextureRender() {
TextureRender() {
mTriangleVertices = ByteBuffer.allocateDirect(
mTriangleVerticesData.length * FLOAT_SIZE_BYTES)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
@ -88,11 +88,11 @@ final class TextureRender {
Matrix.setIdentityM(mSTMatrix, 0);
}
public int getTextureId() {
int getTextureId() {
return mTextureID;
}
public void drawFrame(SurfaceTexture st) {
void drawFrame(SurfaceTexture st) throws TranscodingException {
checkGlError("onDrawFrame start");
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.
*/
public void surfaceCreated() {
void surfaceCreated() throws TranscodingException {
mProgram = createProgram(VERTEX_SHADER, FRAGMENT_SHADER);
if (mProgram == 0) {
throw new RuntimeException("failed creating program");
throw new TranscodingException("failed creating program");
}
maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");
checkGlError("glGetAttribLocation aPosition");
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");
checkGlError("glGetAttribLocation aTextureCoord");
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");
checkGlError("glGetUniformLocation uMVPMatrix");
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");
checkGlError("glGetUniformLocation uSTMatrix");
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];
@ -180,15 +180,15 @@ final class TextureRender {
/**
* Replaces the fragment shader.
*/
public void changeFragmentShader(String fragmentShader) {
public void changeFragmentShader(String fragmentShader) throws TranscodingException {
GLES20.glDeleteProgram(mProgram);
mProgram = createProgram(VERTEX_SHADER, fragmentShader);
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);
checkGlError("glCreateShader type=" + shaderType);
GLES20.glShaderSource(shader, source);
@ -204,7 +204,7 @@ final class TextureRender {
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);
if (vertexShader == 0) {
return 0;
@ -235,11 +235,15 @@ final class TextureRender {
return program;
}
public void checkGlError(String op) {
static void checkGlError(String msg) throws TranscodingException {
boolean failed = false;
int error;
while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
Log.e(TAG, op + ": glError " + error);
throw new RuntimeException(op + ": glError " + error);
Log.e(TAG, msg + ": GLES20 error: 0x" + Integer.toHexString(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 int videoResolution,
final int videoBitrate,
final @NonNull String videoCodec) throws IOException {
final @NonNull String videoCodec) throws IOException, TranscodingException {
final MediaExtractor videoExtractor = input.createExtractor();
final int videoInputTrack = getAndSelectVideoTrackIndex(videoExtractor);
@ -85,7 +85,7 @@ final class VideoTrackConverter {
final long timeTo,
final int videoResolution,
final int videoBitrate,
final @NonNull String videoCodec) throws IOException {
final @NonNull String videoCodec) throws IOException, TranscodingException {
mTimeFrom = timeFrom;
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.
// Do not extract video if we have determined the output format but we are not yet
// ready to mux the frames.