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 462841 1fcfc8417073a8350a1c72560e9654262e2e3878
parent 462512 6a23526fe5168087d7e4132c0705aefcaed5f571
child 542493 69bd3142f001b9984d89e5dcff72cc07c1869806
push id41863
push userbmo:jolin@mozilla.com
push dateWed, 18 Jan 2017 01:20:56 +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/SamplePool.java
--- 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);
     }