diff --git a/encoder/src/main/java/com/pedro/encoder/BaseEncoder.java b/encoder/src/main/java/com/pedro/encoder/BaseEncoder.java index 66d65f747e..c6cbefa456 100644 --- a/encoder/src/main/java/com/pedro/encoder/BaseEncoder.java +++ b/encoder/src/main/java/com/pedro/encoder/BaseEncoder.java @@ -38,19 +38,23 @@ import java.util.concurrent.BlockingQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; /** * Created by pedro on 18/09/19. */ public abstract class BaseEncoder implements EncoderCallback { + private static final int MAX_RESET_ATTEMPTS = 3; + private static final long RESET_ATTEMPTS_WINDOW_MS = 30_000; + protected String TAG = "BaseEncoder"; protected final G711Codec g711Codec = new G711Codec(); private final MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); private HandlerThread handlerThread; - private ExecutorService executorService; + private volatile ExecutorService executorService; protected BlockingQueue queue = new ArrayBlockingQueue<>(80); - protected MediaCodec codec; + protected volatile MediaCodec codec; protected volatile long presentTimeUs; protected volatile boolean running = false; protected boolean isBufferMode = true; @@ -58,6 +62,8 @@ public abstract class BaseEncoder implements EncoderCallback { private MediaCodec.Callback callback; private volatile long oldTimeStamp = 0L; protected boolean shouldReset = true; + private long lastResetTimeMs = 0; + private int resetAttempts = 0; protected boolean prepared = false; private Handler handler; private CodecErrorCallback encoderErrorCallback; @@ -82,6 +88,9 @@ public void restart() { public void start(long startTs) { if (!prepared) throw new IllegalStateException(TAG + " not prepared yet. You must call prepare method before start it"); presentTimeUs = startTs; + shouldReset = true; + lastResetTimeMs = 0; + resetAttempts = 0; start(true); initCodec(); } @@ -111,7 +120,8 @@ private void initCodec() { getDataFromEncoder(); } catch (IllegalStateException e) { Log.i(TAG, "Encoding error", e); - reloadCodec(e); + new Thread(() -> reloadCodec(e), TAG + " recovery").start(); + return; } } }); @@ -144,15 +154,24 @@ private void reloadCodec(IllegalStateException e) { } //Sometimes encoder crash, we will try recover it. Reset encoder a time if crash CodecErrorCallback callback = encoderErrorCallback; - if (callback != null) { - shouldReset = callback.onEncodeError(typeError, e); - } - if (shouldReset) { - Log.e(typeError.name(), "Encoder crashed, trying to recover it"); + if (callback != null) shouldReset = callback.onEncodeError(typeError, e); + if (shouldReset && canReset()) { + Log.e(TAG, typeError.name() + ". Encoder crashed, trying to recover it", e); reset(); + } else { + Log.e(TAG, typeError.name() + ". Encoder crashed, stopping it", e); + stop(); } } + private boolean canReset() { + long now = TimeUtils.getCurrentTimeMillis(); + if (now - lastResetTimeMs > RESET_ATTEMPTS_WINDOW_MS) resetAttempts = 0; + lastResetTimeMs = now; + resetAttempts++; + return resetAttempts <= MAX_RESET_ATTEMPTS; + } + public void stop() { stop(true); } @@ -165,9 +184,7 @@ public void stop(boolean resetTs) { stopImp(); if (handlerThread != null) { if (handlerThread.getLooper() != null) { - if (handlerThread.getLooper().getThread() != null) { - handlerThread.getLooper().getThread().interrupt(); - } + handlerThread.getLooper().getThread().interrupt(); handlerThread.getLooper().quit(); } handlerThread.quit(); @@ -181,7 +198,15 @@ public void stop(boolean resetTs) { handlerThread.getLooper().getThread().join(500); } catch (Exception ignored) { } } - if (executorService != null) executorService.shutdownNow(); + ExecutorService executor = executorService; + if (executor != null) { + executor.shutdownNow(); + try { + executor.awaitTermination(500, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } queue.clear(); queue = new ArrayBlockingQueue<>(80); try { @@ -202,6 +227,8 @@ protected void getDataFromEncoder() throws IllegalStateException { processG711(); return; } + MediaCodec codec = this.codec; + if (codec == null) return; if (isBufferMode) { int inBufferIndex = codec.dequeueInputBuffer(0); if (inBufferIndex >= 0) { diff --git a/encoder/src/main/java/com/pedro/encoder/audio/AudioEncoder.java b/encoder/src/main/java/com/pedro/encoder/audio/AudioEncoder.java index 6291fe0ee8..58eff9db35 100644 --- a/encoder/src/main/java/com/pedro/encoder/audio/AudioEncoder.java +++ b/encoder/src/main/java/com/pedro/encoder/audio/AudioEncoder.java @@ -126,7 +126,6 @@ public boolean prepareAudioEncoder() { @Override public void start(boolean resetTs) { if (resetTs) tsBuffer = 0; - shouldReset = resetTs; Log.i(TAG, "started"); } diff --git a/encoder/src/main/java/com/pedro/encoder/video/VideoEncoder.java b/encoder/src/main/java/com/pedro/encoder/video/VideoEncoder.java index fa5ee97c93..d5d0843e49 100644 --- a/encoder/src/main/java/com/pedro/encoder/video/VideoEncoder.java +++ b/encoder/src/main/java/com/pedro/encoder/video/VideoEncoder.java @@ -41,6 +41,7 @@ import com.pedro.encoder.utils.yuv.YUVUtil; import java.nio.ByteBuffer; +import java.util.ArrayList; import java.util.List; /** @@ -198,7 +199,6 @@ public boolean prepareVideoEncoder(int width, int height, int fps, int bitRate, public void start(boolean resetTs) { if (resetTs) firstTimestamp = 0; forceKey = false; - shouldReset = resetTs; spsPpsSetted = false; if (formatVideoEncoder != FormatVideoEncoder.SURFACE) { YUVUtil.preAllocateBuffers(width * height * 3 / 2); @@ -228,12 +228,13 @@ public boolean reset() { } private FormatVideoEncoder chooseColorDynamically(MediaCodecInfo mediaCodecInfo) { - for (int color : mediaCodecInfo.getCapabilitiesForType(type.getMime()).colorFormats) { - if (color == FormatVideoEncoder.YUV420PLANAR.getFormatCodec()) { - return FormatVideoEncoder.YUV420PLANAR; - } else if (color == FormatVideoEncoder.YUV420SEMIPLANAR.getFormatCodec()) { - return FormatVideoEncoder.YUV420SEMIPLANAR; - } + List colors = new ArrayList<>(); + for (int color : mediaCodecInfo.getCapabilitiesForType(type.getMime()).colorFormats) colors.add(color); + + if (colors.contains(FormatVideoEncoder.YUV420PLANAR.getFormatCodec())) { + return FormatVideoEncoder.YUV420PLANAR; + } else if (colors.contains(FormatVideoEncoder.YUV420SEMIPLANAR.getFormatCodec())) { + return FormatVideoEncoder.YUV420SEMIPLANAR; } return null; }