Bug 1326026 - use bufferless samples for outputs when rendering to surface. r?snorp draft
authorJohn Lin <jolin@mozilla.com>
Thu, 29 Dec 2016 15:39:38 +0800
changeset 454420 a54bf8b920ea004f39c7f772e0ec592eea00c1c2
parent 454355 911b9d23e26113d45a3f92a45d839acf7e34dd4b
child 540704 00c30be841fa7e701f3d80c22c104b156d128d29
push id39923
push userbmo:jolin@mozilla.com
push dateThu, 29 Dec 2016 08:34:45 +0000
reviewerssnorp
bugs1326026
milestone53.0a1
Bug 1326026 - use bufferless samples for outputs when rendering to surface. r?snorp MozReview-Commit-ID: 8gC0QdJUoEk
mobile/android/base/java/org/mozilla/gecko/media/Codec.java
mobile/android/base/java/org/mozilla/gecko/media/Sample.java
mobile/android/base/java/org/mozilla/gecko/media/SamplePool.java
--- 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);
     }