Bug 1326026 - use bufferless samples for outputs when rendering to surface. r=snorp
authorJohn Lin <jolin@mozilla.com>
Thu, 29 Dec 2016 15:39:38 +0800
changeset 374753 4b3a24d9a5012e41b7819be61af5fd5899fa34ec
parent 374752 e8d6755b7a392f5cc2b3aa58df5b185ef620747f
child 374754 304ab4d298b7ba7d5bdfa8f884882cc23685bb84
push id6996
push userjlorenzo@mozilla.com
push dateMon, 06 Mar 2017 20:48:21 +0000
treeherdermozilla-beta@d89512dab048 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssnorp
bugs1326026
milestone53.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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);
     }