Bug 1326026 - use bufferless samples for outputs when rendering to surface. r=snorp
MozReview-Commit-ID: 8gC0QdJUoEk
--- a/mobile/android/base/java/org/mozilla/gecko/media/Codec.java
+++ b/mobile/android/base/java/org/mozilla/gecko/media/Codec.java
@@ -6,17 +6,16 @@ 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.os.TransactionTooLargeException;
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;
@@ -164,67 +163,73 @@ import java.util.concurrent.ConcurrentLi
return;
}
mStopped = true;
reset();
}
}
private class OutputProcessor {
+ private final boolean mRenderToSurface;
private boolean mHasOutputCapacitySet;
private Queue<Integer> mSentIndices = new LinkedList<>();
private Queue<Sample> mSentOutputs = new LinkedList<>();
private boolean mStopped;
+ private OutputProcessor(boolean renderToSurface) {
+ mRenderToSurface = renderToSurface;
+ }
+
private synchronized void onBuffer(int index, MediaCodec.BufferInfo info) {
if (mStopped) {
return;
}
+ Sample output = obtainOutputSample(index, info);
+ try {
+ mSentIndices.add(index);
+ mSentOutputs.add(output);
+ mCallbacks.onOutput(output);
+ } catch (RemoteException e) {
+ // Dead recipient.
+ 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;
}
}
- Sample copy = mSamplePool.obtainOutput(info);
- try {
- if (info.size > 0) {
- copy.buffer.readFromByteBuffer(output, info.offset, info.size);
+
+ 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());
}
- mSentIndices.add(index);
- mSentOutputs.add(copy);
- mCallbacks.onOutput(copy);
- } catch (IOException e) {
- Log.e(LOGTAG, "Fail to read output buffer:" + e.getMessage());
- outputDummy(info);
- } catch (TransactionTooLargeException ttle) {
- Log.e(LOGTAG, "Output is too large:" + ttle.getMessage());
- outputDummy(info);
- } catch (RemoteException e) {
- // Dead recipient.
- e.printStackTrace();
}
- boolean eos = (info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
- if (DEBUG && eos) {
- Log.d(LOGTAG, "output EOS");
- }
- }
-
- private void outputDummy(MediaCodec.BufferInfo info) {
- try {
- if (DEBUG) { Log.d(LOGTAG, "return dummy sample"); }
- mCallbacks.onOutput(Sample.create(null, info, null));
- } catch (RemoteException e) {
- // Dead recipient.
- e.printStackTrace();
- }
+ return sample;
}
private synchronized void onRelease(Sample sample, boolean render) {
Integer i = mSentIndices.poll();
Sample output = mSentOutputs.poll();
if (i == null || output == null) {
Log.d(LOGTAG, "output buffer#" + i + "(" + output + ")" + ": " + sample + " already released");
return;
@@ -326,37 +331,38 @@ import java.util.concurrent.ConcurrentLi
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 (surface != null) {
+ 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();
- mSamplePool = new SamplePool(codecName);
- if (DEBUG) { Log.d(LOGTAG, codec.toString() + " created"); }
+ 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) {
- if (DEBUG) { Log.d(LOGTAG, "FAIL: cannot create codec -- " + codecName); }
+ Log.e(LOGTAG, "FAIL: cannot create codec -- " + codecName);
e.printStackTrace();
return false;
}
}
@Override
public synchronized boolean isAdaptivePlaybackSupported() {
return mIsAdaptivePlaybackSupported;
--- a/mobile/android/base/java/org/mozilla/gecko/media/SamplePool.java
+++ b/mobile/android/base/java/org/mozilla/gecko/media/SamplePool.java
@@ -8,56 +8,62 @@ import android.media.MediaCodec;
import org.mozilla.gecko.mozglue.SharedMemory;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
final class SamplePool {
- private final class Impl {
+ private static final class Impl {
private final String mName;
private int mNextId = 0;
private int mDefaultBufferSize = 4096;
private final List<Sample> mRecycledSamples = new ArrayList<>();
+ private final boolean mBufferless;
- private Impl(String name) {
+ private Impl(String name, boolean bufferless) {
mName = name;
+ mBufferless = bufferless;
}
private void setDefaultBufferSize(int size) {
+ if (mBufferless) {
+ throw new IllegalStateException("Setting buffer size of a bufferless pool is not allowed");
+ }
mDefaultBufferSize = size;
}
- private synchronized Sample allocate(int size) {
- Sample sample;
+ private synchronized Sample obtain(int size) {
if (!mRecycledSamples.isEmpty()) {
- sample = mRecycledSamples.remove(0);
- sample.info.set(0, 0, 0, 0);
- } else {
- SharedMemory shm = null;
- try {
- shm = new SharedMemory(mNextId++, Math.max(size, mDefaultBufferSize));
- } catch (NoSuchMethodException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- }
- if (shm != null) {
- sample = Sample.create(shm);
- } else {
- sample = Sample.create();
- }
+ return mRecycledSamples.remove(0);
}
- return sample;
+ if (mBufferless) {
+ return Sample.create();
+ } else {
+ return allocateSharedMemorySample(size);
+ }
+ }
+
+ private Sample allocateSharedMemorySample(int size) {
+ SharedMemory shm = null;
+ try {
+ shm = new SharedMemory(mNextId++, Math.max(size, mDefaultBufferSize));
+ } catch (NoSuchMethodException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ return shm != null ? Sample.create(shm) : Sample.create();
}
private synchronized void recycle(Sample recycled) {
- if (recycled.buffer.capacity() >= mDefaultBufferSize) {
+ if (mBufferless || recycled.buffer.capacity() >= mDefaultBufferSize) {
mRecycledSamples.add(recycled);
} else {
recycled.dispose();
}
}
private synchronized void clear() {
for (Sample s : mRecycledSamples) {
@@ -71,35 +77,38 @@ final class SamplePool {
protected void finalize() {
clear();
}
}
private final Impl mInputs;
private final Impl mOutputs;
- /* package */ SamplePool(String name) {
- mInputs = new Impl(name + " input buffer pool");
- mOutputs = new Impl(name + " output buffer pool");
+ /* package */ SamplePool(String name, boolean renderToSurface) {
+ mInputs = new Impl(name + " input sample pool", false);
+ // Buffers are useless when rendering to surface.
+ mOutputs = new Impl(name + " output sample pool", renderToSurface);
}
/* package */ void setInputBufferSize(int size) {
mInputs.setDefaultBufferSize(size);
}
/* package */ void setOutputBufferSize(int size) {
mOutputs.setDefaultBufferSize(size);
}
/* package */ Sample obtainInput(int size) {
- return mInputs.allocate(size);
+ Sample input = mInputs.obtain(size);
+ input.info.set(0, 0, 0, 0);
+ return input;
}
/* package */ Sample obtainOutput(MediaCodec.BufferInfo info) {
- Sample output = mOutputs.allocate(info.size);
+ Sample output = mOutputs.obtain(info.size);
output.info.set(0, info.size, info.presentationTimeUs, info.flags);
return output;
}
/* package */ void recycleInput(Sample sample) {
sample.cryptoInfo = null;
mInputs.recycle(sample);
}