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
@@ -156,67 +156,73 @@ import java.util.concurrent.ConcurrentLi
private synchronized void stop() {
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;
@@ -312,37 +318,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/Sample.java
+++ b/mobile/android/base/java/org/mozilla/gecko/media/Sample.java
@@ -14,98 +14,37 @@ import org.mozilla.gecko.annotation.Wrap
import org.mozilla.gecko.mozglue.SharedMemBuffer;
import org.mozilla.gecko.mozglue.SharedMemory;
import java.io.IOException;
import java.nio.ByteBuffer;
// Parcelable carrying input/output sample data and info cross process.
public final class Sample implements Parcelable {
- public static final Sample EOS;
+ /* package */ static final Sample EOS;
static {
BufferInfo eosInfo = new BufferInfo();
eosInfo.set(0, 0, Long.MIN_VALUE, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
EOS = new Sample(null, eosInfo, null);
}
public interface Buffer extends Parcelable {
int capacity();
void readFromByteBuffer(ByteBuffer src, int offset, int size) throws IOException;
void writeToByteBuffer(ByteBuffer dest, int offset, int size) throws IOException;
void dispose();
}
- private static final class ArrayBuffer implements Buffer {
- private byte[] mArray;
-
- public static final Creator<ArrayBuffer> CREATOR = new Creator<ArrayBuffer>() {
- @Override
- public ArrayBuffer createFromParcel(Parcel in) {
- return new ArrayBuffer(in);
- }
-
- @Override
- public ArrayBuffer[] newArray(int size) {
- return new ArrayBuffer[size];
- }
- };
-
- private ArrayBuffer(Parcel in) {
- mArray = in.createByteArray();
- }
-
- private ArrayBuffer(byte[] bytes) { mArray = bytes; }
-
- @Override
- public int describeContents() { return 0; }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeByteArray(mArray);
- }
-
- @Override
- public int capacity() {
- return mArray != null ? mArray.length : 0;
- }
-
- @Override
- public void readFromByteBuffer(ByteBuffer src, int offset, int size) throws IOException {
- src.position(offset);
- if (mArray == null || mArray.length != size) {
- mArray = new byte[size];
- }
- src.get(mArray, 0, size);
- }
-
- @Override
- public void writeToByteBuffer(ByteBuffer dest, int offset, int size) throws IOException {
- dest.put(mArray, offset, size);
- }
-
- @Override
- public void dispose() {
- mArray = null;
- }
- }
-
public Buffer buffer;
@WrapForJNI
public BufferInfo info;
public CryptoInfo cryptoInfo;
- public static Sample create() { return create(null, new BufferInfo(), null); }
-
- public static Sample create(ByteBuffer src, BufferInfo info, CryptoInfo cryptoInfo) {
- ArrayBuffer buffer = new ArrayBuffer(byteArrayFromBuffer(src, info.offset, info.size));
-
- BufferInfo bufferInfo = new BufferInfo();
- bufferInfo.set(0, info.size, info.presentationTimeUs, info.flags);
-
- return new Sample(buffer, bufferInfo, cryptoInfo);
+ public static Sample create() {
+ return new Sample(null, new BufferInfo(), null);
}
public static Sample create(SharedMemory sharedMem) {
return new Sample(new SharedMemBuffer(sharedMem), new BufferInfo(), null);
}
private Sample(Buffer bytes, BufferInfo info, CryptoInfo cryptoInfo) {
buffer = bytes;
@@ -169,17 +108,17 @@ public final class Sample implements Par
if (buffer != null) {
buffer.dispose();
buffer = null;
}
info = null;
cryptoInfo = null;
}
- public boolean isEOS() {
+ /* package */ boolean isEOS() {
return (this == EOS) ||
((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0);
}
public static final Creator<Sample> CREATOR = new Creator<Sample>() {
@Override
public Sample createFromParcel(Parcel in) {
return new Sample(in);
--- 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);
}