Bug 1353459 - handle queueInputBuffer exceptions. r=esawin. a=gchang
MozReview-Commit-ID: 1Tm0vcl3Uv7
new file mode 100644
--- /dev/null
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/Codec.java
@@ -0,0 +1,551 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.media;
+
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecList;
+import android.media.MediaCrypto;
+import android.media.MediaFormat;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.Surface;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.LinkedList;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+/* package */ final class Codec extends ICodec.Stub implements IBinder.DeathRecipient {
+ private static final String LOGTAG = "GeckoRemoteCodec";
+ private static final boolean DEBUG = false;
+
+ public enum Error {
+ DECODE, FATAL
+ }
+
+ private final class Callbacks implements AsyncCodec.Callbacks {
+ @Override
+ public void onInputBufferAvailable(AsyncCodec codec, int index) {
+ mInputProcessor.onBuffer(index);
+ }
+
+ @Override
+ public void onOutputBufferAvailable(AsyncCodec codec, int index, MediaCodec.BufferInfo info) {
+ mOutputProcessor.onBuffer(index, info);
+ }
+
+ @Override
+ public void onError(AsyncCodec codec, int error) {
+ reportError(Error.FATAL, new Exception("codec error:" + error));
+ }
+
+ @Override
+ public void onOutputFormatChanged(AsyncCodec codec, MediaFormat format) {
+ mOutputProcessor.onFormatChanged(format);
+ }
+ }
+
+ private static final class Input {
+ public final Sample sample;
+ public boolean reported;
+
+ public Input(final Sample sample) {
+ this.sample = sample;
+ }
+ }
+
+ private final class InputProcessor {
+ private boolean mHasInputCapacitySet;
+ private Queue<Integer> mAvailableInputBuffers = new LinkedList<>();
+ private Queue<Sample> mDequeuedSamples = new LinkedList<>();
+ private Queue<Input> mInputSamples = new LinkedList<>();
+ private boolean mStopped;
+
+ private synchronized Sample onAllocate(int size) {
+ Sample sample = mSamplePool.obtainInput(size);
+ mDequeuedSamples.add(sample);
+ return sample;
+ }
+
+ private synchronized void onSample(Sample sample) {
+ if (sample == null) {
+ // Ignore empty input.
+ mSamplePool.recycleInput(mDequeuedSamples.remove());
+ Log.w(LOGTAG, "WARN: empty input sample");
+ return;
+ }
+
+ if (sample.isEOS()) {
+ queueSample(sample);
+ return;
+ }
+
+ Sample dequeued = mDequeuedSamples.remove();
+ dequeued.info = sample.info;
+ dequeued.cryptoInfo = sample.cryptoInfo;
+ queueSample(dequeued);
+
+ sample.dispose();
+ }
+
+ private void queueSample(Sample sample) {
+ if (!mInputSamples.offer(new Input(sample))) {
+ reportError(Error.FATAL, new Exception("FAIL: input sample queue is full"));
+ return;
+ }
+
+ try {
+ feedSampleToBuffer();
+ } catch (Exception e) {
+ reportError(Error.FATAL, e);
+ }
+ }
+
+ private synchronized void onBuffer(int index) {
+ if (mStopped) {
+ return;
+ }
+
+ if (!mHasInputCapacitySet) {
+ int capacity = mCodec.getInputBuffer(index).capacity();
+ if (capacity > 0) {
+ mSamplePool.setInputBufferSize(capacity);
+ mHasInputCapacitySet = true;
+ }
+ }
+
+ if (mAvailableInputBuffers.offer(index)) {
+ feedSampleToBuffer();
+ } else {
+ reportError(Error.FATAL, new Exception("FAIL: input buffer queue is full"));
+ }
+
+ }
+
+ private void feedSampleToBuffer() {
+ while (!mAvailableInputBuffers.isEmpty() && !mInputSamples.isEmpty()) {
+ int index = mAvailableInputBuffers.poll();
+ int len = 0;
+ final Sample sample = mInputSamples.poll().sample;
+ long pts = sample.info.presentationTimeUs;
+ int flags = sample.info.flags;
+ MediaCodec.CryptoInfo cryptoInfo = sample.cryptoInfo;
+ if (!sample.isEOS() && sample.buffer != null) {
+ len = sample.info.size;
+ ByteBuffer buf = mCodec.getInputBuffer(index);
+ try {
+ sample.writeToByteBuffer(buf);
+ } catch (IOException e) {
+ e.printStackTrace();
+ len = 0;
+ }
+ mSamplePool.recycleInput(sample);
+ }
+
+ try {
+ if (cryptoInfo != null && len > 0) {
+ mCodec.queueSecureInputBuffer(index, 0, cryptoInfo, pts, flags);
+ } else {
+ mCodec.queueInputBuffer(index, 0, len, pts, flags);
+ }
+ mCallbacks.onInputQueued(pts);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ } catch (Exception e) {
+ reportError(Error.FATAL, e);
+ return;
+ }
+ }
+ reportPendingInputs();
+ }
+
+ private void reportPendingInputs() {
+ try {
+ for (Input i : mInputSamples) {
+ if (!i.reported) {
+ i.reported = true;
+ mCallbacks.onInputPending(i.sample.info.presentationTimeUs);
+ }
+ }
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private synchronized void reset() {
+ for (Input i : mInputSamples) {
+ if (!i.sample.isEOS()) {
+ mSamplePool.recycleInput(i.sample);
+ }
+ }
+ mInputSamples.clear();
+
+ for (Sample s : mDequeuedSamples) {
+ mSamplePool.recycleInput(s);
+ }
+ mDequeuedSamples.clear();
+
+ mAvailableInputBuffers.clear();
+ }
+
+ private synchronized void start() {
+ if (!mStopped) {
+ return;
+ }
+ mStopped = false;
+ }
+
+ private synchronized void stop() {
+ if (mStopped) {
+ return;
+ }
+ mStopped = true;
+ reset();
+ }
+ }
+
+ private static final class Output {
+ public final Sample sample;
+ public final int index;
+
+ public Output(final Sample sample, int index) {
+ this.sample = sample;
+ this.index = index;
+ }
+ }
+
+ private class OutputProcessor {
+ private final boolean mRenderToSurface;
+ private boolean mHasOutputCapacitySet;
+ private Queue<Output> mSentOutputs = new LinkedList<>();
+ private boolean mStopped;
+
+ private OutputProcessor(boolean renderToSurface) {
+ mRenderToSurface = renderToSurface;
+ }
+
+ private synchronized void onBuffer(int index, MediaCodec.BufferInfo info) {
+ if (mStopped) {
+ return;
+ }
+
+ try {
+ Sample output = obtainOutputSample(index, info);
+ mSentOutputs.add(new Output(output, index));
+ mCallbacks.onOutput(output);
+ } catch (Exception e) {
+ e.printStackTrace();
+ mCodec.releaseOutputBuffer(index, false);
+ }
+
+ boolean eos = (info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
+ if (DEBUG && eos) {
+ Log.d(LOGTAG, "output EOS");
+ }
+ }
+
+ private Sample obtainOutputSample(int index, MediaCodec.BufferInfo info) {
+ Sample sample = mSamplePool.obtainOutput(info);
+
+ if (mRenderToSurface) {
+ return sample;
+ }
+
+ ByteBuffer output = mCodec.getOutputBuffer(index);
+ if (!mHasOutputCapacitySet) {
+ int capacity = output.capacity();
+ if (capacity > 0) {
+ mSamplePool.setOutputBufferSize(capacity);
+ mHasOutputCapacitySet = true;
+ }
+ }
+
+ if (info.size > 0) {
+ try {
+ sample.buffer.readFromByteBuffer(output, info.offset, info.size);
+ } catch (IOException e) {
+ Log.e(LOGTAG, "Fail to read output buffer:" + e.getMessage());
+ }
+ }
+
+ return sample;
+ }
+
+ private synchronized void onRelease(Sample sample, boolean render) {
+ final Output output = mSentOutputs.poll();
+ if (output == null) {
+ if (DEBUG) { Log.d(LOGTAG, sample + " already released"); }
+ return;
+ }
+ mCodec.releaseOutputBuffer(output.index, render);
+ mSamplePool.recycleOutput(output.sample);
+
+ sample.dispose();
+ }
+
+ private void onFormatChanged(MediaFormat format) {
+ try {
+ mCallbacks.onOutputFormatChanged(new FormatParam(format));
+ } catch (RemoteException re) {
+ // Dead recipient.
+ re.printStackTrace();
+ }
+ }
+
+ private synchronized void reset() {
+ for (final Output o : mSentOutputs) {
+ mCodec.releaseOutputBuffer(o.index, false);
+ mSamplePool.recycleOutput(o.sample);
+ }
+ mSentOutputs.clear();
+ }
+
+ private synchronized void start() {
+ if (!mStopped) {
+ return;
+ }
+ mStopped = false;
+ }
+
+ private synchronized void stop() {
+ if (mStopped) {
+ return;
+ }
+ mStopped = true;
+ reset();
+ }
+ }
+
+ private volatile ICodecCallbacks mCallbacks;
+ private AsyncCodec mCodec;
+ private InputProcessor mInputProcessor;
+ private OutputProcessor mOutputProcessor;
+ private SamplePool mSamplePool;
+ // Value will be updated after configure called.
+ private volatile boolean mIsAdaptivePlaybackSupported = false;
+
+ public synchronized void setCallbacks(ICodecCallbacks callbacks) throws RemoteException {
+ mCallbacks = callbacks;
+ callbacks.asBinder().linkToDeath(this, 0);
+ }
+
+ // IBinder.DeathRecipient
+ @Override
+ public synchronized void binderDied() {
+ Log.e(LOGTAG, "Callbacks is dead");
+ try {
+ release();
+ } catch (RemoteException e) {
+ // Nowhere to report the error.
+ }
+ }
+
+ @Override
+ public synchronized boolean configure(FormatParam format,
+ Surface surface,
+ int flags,
+ String drmStubId) throws RemoteException {
+ if (mCallbacks == null) {
+ Log.e(LOGTAG, "FAIL: callbacks must be set before calling configure()");
+ return false;
+ }
+
+ if (mCodec != null) {
+ if (DEBUG) { Log.d(LOGTAG, "release existing codec: " + mCodec); }
+ releaseCodec();
+ }
+
+ if (DEBUG) { Log.d(LOGTAG, "configure " + this); }
+
+ MediaFormat fmt = format.asFormat();
+ String codecName = getCodecForFormat(fmt, flags == MediaCodec.CONFIGURE_FLAG_ENCODE ? true : false);
+ if (codecName == null) {
+ Log.e(LOGTAG, "FAIL: cannot find codec");
+ return false;
+ }
+
+ try {
+ AsyncCodec codec = AsyncCodecFactory.create(codecName);
+
+ MediaCrypto crypto = RemoteMediaDrmBridgeStub.getMediaCrypto(drmStubId);
+ if (DEBUG) {
+ boolean hasCrypto = crypto != null;
+ Log.d(LOGTAG, "configure mediacodec with crypto(" + hasCrypto + ") / Id :" + drmStubId);
+ }
+
+ codec.setCallbacks(new Callbacks(), null);
+
+ boolean renderToSurface = surface != null;
+ // Video decoder should config with adaptive playback capability.
+ if (renderToSurface) {
+ mIsAdaptivePlaybackSupported = codec.isAdaptivePlaybackSupported(
+ fmt.getString(MediaFormat.KEY_MIME));
+ if (mIsAdaptivePlaybackSupported) {
+ if (DEBUG) { Log.d(LOGTAG, "codec supports adaptive playback = " + mIsAdaptivePlaybackSupported); }
+ // TODO: may need to find a way to not use hard code to decide the max w/h.
+ fmt.setInteger(MediaFormat.KEY_MAX_WIDTH, 1920);
+ fmt.setInteger(MediaFormat.KEY_MAX_HEIGHT, 1080);
+ }
+ }
+
+ codec.configure(fmt, surface, crypto, flags);
+ mCodec = codec;
+ mInputProcessor = new InputProcessor();
+ mOutputProcessor = new OutputProcessor(renderToSurface);
+ mSamplePool = new SamplePool(codecName, renderToSurface);
+ if (DEBUG) { Log.d(LOGTAG, codec.toString() + " created. Render to surface?" + renderToSurface); }
+ return true;
+ } catch (Exception e) {
+ Log.e(LOGTAG, "FAIL: cannot create codec -- " + codecName);
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+ @Override
+ public synchronized boolean isAdaptivePlaybackSupported() {
+ return mIsAdaptivePlaybackSupported;
+ }
+
+ private void releaseCodec() {
+ try {
+ // In case Codec.stop() is not called yet.
+ mInputProcessor.stop();
+ mOutputProcessor.stop();
+
+ mCodec.release();
+ } catch (Exception e) {
+ reportError(Error.FATAL, e);
+ }
+ mCodec = null;
+ }
+
+ private String getCodecForFormat(MediaFormat format, boolean isEncoder) {
+ String mime = format.getString(MediaFormat.KEY_MIME);
+ if (mime == null) {
+ return null;
+ }
+ int numCodecs = MediaCodecList.getCodecCount();
+ for (int i = 0; i < numCodecs; i++) {
+ MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i);
+ if (info.isEncoder() == !isEncoder) {
+ continue;
+ }
+ String[] types = info.getSupportedTypes();
+ for (String t : types) {
+ if (t.equalsIgnoreCase(mime)) {
+ return info.getName();
+ }
+ }
+ }
+ return null;
+ // TODO: API 21+ is simpler.
+ //static MediaCodecList sCodecList = new MediaCodecList(MediaCodecList.ALL_CODECS);
+ //return sCodecList.findDecoderForFormat(format);
+ }
+
+ @Override
+ public synchronized void start() throws RemoteException {
+ if (DEBUG) { Log.d(LOGTAG, "start " + this); }
+ mInputProcessor.start();
+ mOutputProcessor.start();
+ try {
+ mCodec.start();
+ } catch (Exception e) {
+ reportError(Error.FATAL, e);
+ }
+ }
+
+ private void reportError(Error error, Exception e) {
+ if (e != null) {
+ e.printStackTrace();
+ }
+ try {
+ mCallbacks.onError(error == Error.FATAL);
+ } catch (RemoteException re) {
+ re.printStackTrace();
+ }
+ }
+
+ @Override
+ public synchronized void stop() throws RemoteException {
+ if (DEBUG) { Log.d(LOGTAG, "stop " + this); }
+ try {
+ mInputProcessor.stop();
+ mOutputProcessor.stop();
+
+ mCodec.stop();
+ } catch (Exception e) {
+ reportError(Error.FATAL, e);
+ }
+ }
+
+ @Override
+ public synchronized void flush() throws RemoteException {
+ if (DEBUG) { Log.d(LOGTAG, "flush " + this); }
+ try {
+ mInputProcessor.stop();
+ mOutputProcessor.stop();
+
+ mCodec.flush();
+ if (DEBUG) { Log.d(LOGTAG, "flushed " + this); }
+ mInputProcessor.start();
+ mOutputProcessor.start();
+ mCodec.resumeReceivingInputs();
+ } catch (Exception e) {
+ reportError(Error.FATAL, e);
+ }
+ }
+
+ @Override
+ public synchronized Sample dequeueInput(int size) throws RemoteException {
+ try {
+ return mInputProcessor.onAllocate(size);
+ } catch (Exception e) {
+ // Translate allocation error to remote exception.
+ throw new RemoteException(e.getMessage());
+ }
+ }
+
+ @Override
+ public synchronized void queueInput(Sample sample) throws RemoteException {
+ try {
+ mInputProcessor.onSample(sample);
+ } catch (Exception e) {
+ throw new RemoteException(e.getMessage());
+ }
+ }
+
+ @Override
+ public synchronized void setRates(int newBitRate) {
+ try {
+ mCodec.setRates(newBitRate);
+ } catch (Exception e) {
+ reportError(Error.FATAL, e);
+ }
+ }
+
+ @Override
+ public synchronized void releaseOutput(Sample sample, boolean render) {
+ try {
+ mOutputProcessor.onRelease(sample, render);
+ } catch (Exception e) {
+ reportError(Error.FATAL, e);
+ }
+ }
+
+ @Override
+ public synchronized void release() throws RemoteException {
+ if (DEBUG) { Log.d(LOGTAG, "release " + this); }
+ releaseCodec();
+ mSamplePool.reset();
+ mSamplePool = null;
+ mCallbacks.asBinder().unlinkToDeath(this, 0);
+ mCallbacks = null;
+ }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/CodecProxy.java
@@ -0,0 +1,326 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.media;
+
+import android.media.MediaCodec;
+import android.media.MediaCodec.BufferInfo;
+import android.media.MediaCodec.CryptoInfo;
+import android.media.MediaFormat;
+import android.os.DeadObjectException;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.Surface;
+
+import org.mozilla.gecko.annotation.WrapForJNI;
+import org.mozilla.gecko.mozglue.JNIObject;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+// Proxy class of ICodec binder.
+public final class CodecProxy {
+ private static final String LOGTAG = "GeckoRemoteCodecProxy";
+ private static final boolean DEBUG = false;
+
+ private ICodec mRemote;
+ private boolean mIsEncoder;
+ private FormatParam mFormat;
+ private Surface mOutputSurface;
+ private CallbacksForwarder mCallbacks;
+ private String mRemoteDrmStubId;
+ private Queue<Sample> mSurfaceOutputs = new ConcurrentLinkedQueue<>();
+
+ public interface Callbacks {
+ void onInputStatus(long timestamp, boolean processed);
+ void onOutputFormatChanged(MediaFormat format);
+ void onOutput(Sample output);
+ void onError(boolean fatal);
+ }
+
+ @WrapForJNI
+ public static class NativeCallbacks extends JNIObject implements Callbacks {
+ public native void onInputStatus(long timestamp, boolean processed);
+ public native void onOutputFormatChanged(MediaFormat format);
+ public native void onOutput(Sample output);
+ public native void onError(boolean fatal);
+
+ @Override // JNIObject
+ protected void disposeNative() {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ private class CallbacksForwarder extends ICodecCallbacks.Stub {
+ private final Callbacks mCallbacks;
+ private boolean mEndOfInput;
+
+ CallbacksForwarder(Callbacks callbacks) {
+ mCallbacks = callbacks;
+ }
+
+ @Override
+ public synchronized void onInputQueued(long timestamp) throws RemoteException {
+ if (!mEndOfInput) {
+ mCallbacks.onInputStatus(timestamp, true /* processed */);
+ }
+ }
+
+ @Override
+ public synchronized void onInputPending(long timestamp) throws RemoteException {
+ if (!mEndOfInput) {
+ mCallbacks.onInputStatus(timestamp, false /* processed */);
+ }
+ }
+
+ @Override
+ public void onOutputFormatChanged(FormatParam format) throws RemoteException {
+ mCallbacks.onOutputFormatChanged(format.asFormat());
+ }
+
+ @Override
+ public void onOutput(Sample sample) throws RemoteException {
+ if (mOutputSurface != null) {
+ // Don't render to surface just yet. Callback will make that happen when it's time.
+ mSurfaceOutputs.offer(sample);
+ mCallbacks.onOutput(sample);
+ } else {
+ // Non-surface output needs no rendering.
+ mCallbacks.onOutput(sample);
+ mRemote.releaseOutput(sample, false);
+ sample.dispose();
+ }
+ }
+
+ @Override
+ public void onError(boolean fatal) throws RemoteException {
+ reportError(fatal);
+ }
+
+ private void reportError(boolean fatal) {
+ mCallbacks.onError(fatal);
+ }
+
+ private void setEndOfInput(boolean end) {
+ mEndOfInput = end;
+ }
+ }
+
+ @WrapForJNI
+ public static CodecProxy create(boolean isEncoder,
+ MediaFormat format,
+ Surface surface,
+ Callbacks callbacks,
+ String drmStubId) {
+ return RemoteManager.getInstance().createCodec(isEncoder, format, surface, callbacks, drmStubId);
+ }
+
+ public static CodecProxy createCodecProxy(boolean isEncoder,
+ MediaFormat format,
+ Surface surface,
+ Callbacks callbacks,
+ String drmStubId) {
+ return new CodecProxy(isEncoder, format, surface, callbacks, drmStubId);
+ }
+
+ private CodecProxy(boolean isEncoder, MediaFormat format, Surface surface, Callbacks callbacks, String drmStubId) {
+ mIsEncoder = isEncoder;
+ mFormat = new FormatParam(format);
+ mOutputSurface = surface;
+ mRemoteDrmStubId = drmStubId;
+ mCallbacks = new CallbacksForwarder(callbacks);
+ }
+
+ boolean init(ICodec remote) {
+ try {
+ remote.setCallbacks(mCallbacks);
+ if (!remote.configure(mFormat, mOutputSurface, mIsEncoder ? MediaCodec.CONFIGURE_FLAG_ENCODE : 0, mRemoteDrmStubId)) {
+ return false;
+ }
+ remote.start();
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ return false;
+ }
+
+ mRemote = remote;
+ return true;
+ }
+
+ boolean deinit() {
+ try {
+ mRemote.stop();
+ mRemote.release();
+ mRemote = null;
+ return true;
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+ @WrapForJNI
+ public synchronized boolean isAdaptivePlaybackSupported()
+ {
+ if (mRemote == null) {
+ Log.e(LOGTAG, "cannot check isAdaptivePlaybackSupported with an ended codec");
+ return false;
+ }
+ try {
+ return mRemote.isAdaptivePlaybackSupported();
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+ @WrapForJNI
+ public synchronized boolean input(ByteBuffer bytes, BufferInfo info, CryptoInfo cryptoInfo) {
+ if (mRemote == null) {
+ Log.e(LOGTAG, "cannot send input to an ended codec");
+ return false;
+ }
+
+ boolean eos = info.flags == MediaCodec.BUFFER_FLAG_END_OF_STREAM;
+ mCallbacks.setEndOfInput(eos);
+
+ if (eos) {
+ return sendInput(Sample.EOS);
+ }
+
+ try {
+ return sendInput(mRemote.dequeueInput(info.size).set(bytes, info, cryptoInfo));
+ } catch (RemoteException | NullPointerException e) {
+ Log.e(LOGTAG, "fail to dequeue input buffer", e);
+ return false;
+ } catch (IOException e) {
+ Log.e(LOGTAG, "fail to copy input data.", e);
+ // Balance dequeue/queue.
+ return sendInput(null);
+ }
+ }
+
+ private boolean sendInput(Sample sample) {
+ try {
+ mRemote.queueInput(sample);
+ if (sample != null) {
+ sample.dispose();
+ }
+ } catch (Exception e) {
+ Log.e(LOGTAG, "fail to queue input:" + sample, e);
+ return false;
+ }
+
+ return true;
+ }
+
+ @WrapForJNI
+ public synchronized boolean flush() {
+ if (mRemote == null) {
+ Log.e(LOGTAG, "cannot flush an ended codec");
+ return false;
+ }
+ try {
+ if (DEBUG) { Log.d(LOGTAG, "flush " + this); }
+ mRemote.flush();
+ } catch (DeadObjectException e) {
+ return false;
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ return false;
+ }
+ return true;
+ }
+
+ @WrapForJNI
+ public synchronized boolean release() {
+ if (mRemote == null) {
+ Log.w(LOGTAG, "codec already ended");
+ return true;
+ }
+ if (DEBUG) { Log.d(LOGTAG, "release " + this); }
+
+ if (!mSurfaceOutputs.isEmpty()) {
+ // Flushing output buffers to surface may cause some frames to be skipped and
+ // should not happen unless caller release codec before processing all buffers.
+ Log.w(LOGTAG, "release codec when " + mSurfaceOutputs.size() + " output buffers unhandled");
+ try {
+ for (Sample s : mSurfaceOutputs) {
+ mRemote.releaseOutput(s, true);
+ }
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ mSurfaceOutputs.clear();
+ }
+
+ try {
+ RemoteManager.getInstance().releaseCodec(this);
+ } catch (DeadObjectException e) {
+ return false;
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ return false;
+ }
+ return true;
+ }
+
+ @WrapForJNI
+ public synchronized boolean setRates(int newBitRate) {
+ if (!mIsEncoder) {
+ Log.w(LOGTAG, "this api is encoder-only");
+ return false;
+ }
+
+ if (android.os.Build.VERSION.SDK_INT < 19) {
+ Log.w(LOGTAG, "this api was added in API level 19");
+ return false;
+ }
+
+ if (mRemote == null) {
+ Log.w(LOGTAG, "codec already ended");
+ return true;
+ }
+
+ try {
+ mRemote.setRates(newBitRate);
+ } catch (RemoteException e) {
+ Log.e(LOGTAG, "remote fail to set rates:" + newBitRate);
+ e.printStackTrace();
+ }
+ return true;
+ }
+
+ @WrapForJNI
+ public synchronized boolean releaseOutput(Sample sample, boolean render) {
+ if (!mSurfaceOutputs.remove(sample)) {
+ if (mRemote != null) Log.w(LOGTAG, "already released: " + sample);
+ return true;
+ }
+
+ if (mRemote == null) {
+ Log.w(LOGTAG, "codec already ended");
+ sample.dispose();
+ return true;
+ }
+
+ if (DEBUG && !render) { Log.d(LOGTAG, "drop output:" + sample.info.presentationTimeUs); }
+
+ try {
+ mRemote.releaseOutput(sample, render);
+ } catch (RemoteException e) {
+ Log.e(LOGTAG, "remote fail to render output:" + sample.info.presentationTimeUs);
+ e.printStackTrace();
+ }
+ sample.dispose();
+
+ return true;
+ }
+
+ /* package */ void reportError(boolean fatal) {
+ mCallbacks.reportError(fatal);
+ }
+}