From 9d4d55e04dc6b861fe89917e6a22bd3487c1124c Mon Sep 17 00:00:00 2001 From: John Lin Date: Thu, 6 Oct 2016 17:05:20 +0800 Subject: [PATCH] Bug 1295106 - Part 5: allocate samples from a pool. r=snorp MozReview-Commit-ID: CIn9CH9k9i4 --HG-- extra : rebase_source : 6e0178881ee8c46832c064027907f43310f6f548 --- .../java/org/mozilla/gecko/media/Codec.java | 72 ++++++++++- .../org/mozilla/gecko/media/SamplePool.java | 115 ++++++++++++++++++ mobile/android/base/moz.build | 1 + 3 files changed, 182 insertions(+), 6 deletions(-) create mode 100644 mobile/android/base/java/org/mozilla/gecko/media/SamplePool.java diff --git a/mobile/android/base/java/org/mozilla/gecko/media/Codec.java b/mobile/android/base/java/org/mozilla/gecko/media/Codec.java index 5a79d70a608c..331f60a65d5e 100644 --- a/mobile/android/base/java/org/mozilla/gecko/media/Codec.java +++ b/mobile/android/base/java/org/mozilla/gecko/media/Codec.java @@ -17,6 +17,7 @@ import android.view.Surface; import java.io.IOException; import java.nio.ByteBuffer; import java.util.LinkedList; +import java.util.NoSuchElementException; import java.util.Queue; /* package */ final class Codec extends ICodec.Stub implements IBinder.DeathRecipient { @@ -29,6 +30,8 @@ import java.util.Queue; private final class Callbacks implements AsyncCodec.Callbacks { private ICodecCallbacks mRemote; + private boolean mHasInputCapacitySet; + private boolean mHasOutputCapacitySet; public Callbacks(ICodecCallbacks remote) { mRemote = remote; @@ -40,6 +43,13 @@ import java.util.Queue; // Flush invalidates all buffers. return; } + if (!mHasInputCapacitySet) { + int capacity = codec.getInputBuffer(index).capacity(); + if (capacity > 0) { + mSamplePool.setInputBufferSize(capacity); + mHasInputCapacitySet = true; + } + } if (!mInputProcessor.onBuffer(index)) { reportError(Error.FATAL, new Exception("FAIL: input buffer queue is full")); } @@ -51,8 +61,24 @@ import java.util.Queue; // Flush invalidates all buffers. return; } + ByteBuffer output = codec.getOutputBuffer(index); + if (!mHasOutputCapacitySet) { + int capacity = output.capacity(); + if (capacity > 0) { + mSamplePool.setOutputBufferSize(capacity); + mHasOutputCapacitySet = true; + } + } + Sample copy = mSamplePool.obtainOutput(info); try { - mRemote.onOutput(Sample.create(codec.getOutputBuffer(index), info, null)); + if (info.size > 0) { + copy.buffer.readFromByteBuffer(output, info.offset, info.size); + } + mSentOutputs.add(copy); + mRemote.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); @@ -60,6 +86,7 @@ import java.util.Queue; // Dead recipient. e.printStackTrace(); } + mCodec.releaseOutputBuffer(index, true); boolean eos = (info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0; if (DEBUG && eos) { @@ -94,10 +121,29 @@ import java.util.Queue; } private final class InputProcessor { - private Queue mInputSamples = new LinkedList(); - private Queue mAvailableInputBuffers = new LinkedList(); + private Queue mInputSamples = new LinkedList<>(); + private Queue mAvailableInputBuffers = new LinkedList<>(); + private Queue mDequeuedSamples = new LinkedList<>(); + + private synchronized Sample onAllocate(int size) { + Sample sample = mSamplePool.obtainInput(size); + mDequeuedSamples.add(sample); + return sample; + } private synchronized boolean onSample(Sample sample) { + if (sample == null) { + return false; + } + + if (!sample.isEOS()) { + Sample temp = sample; + sample = mDequeuedSamples.remove(); + sample.info = temp.info; + sample.cryptoInfo = temp.cryptoInfo; + temp.dispose(); + } + if (!mInputSamples.offer(sample)) { return false; } @@ -116,8 +162,10 @@ import java.util.Queue; private void feedSampleToBuffer() { while (!mAvailableInputBuffers.isEmpty() && !mInputSamples.isEmpty()) { int index = mAvailableInputBuffers.poll(); - Sample sample = mInputSamples.poll(); int len = 0; + Sample sample = mInputSamples.poll(); + long pts = sample.info.presentationTimeUs; + int flags = sample.info.flags; if (!sample.isEOS() && sample.buffer != null) { len = sample.info.size; ByteBuffer buf = mCodec.getInputBuffer(index); @@ -129,8 +177,9 @@ import java.util.Queue; } catch (RemoteException e) { e.printStackTrace(); } + mSamplePool.recycleInput(sample); } - mCodec.queueInputBuffer(index, 0, len, sample.info.presentationTimeUs, sample.info.flags); + mCodec.queueInputBuffer(index, 0, len, pts, flags); } } @@ -143,6 +192,8 @@ import java.util.Queue; private AsyncCodec mCodec; private InputProcessor mInputProcessor; private volatile boolean mFlushing = false; + private SamplePool mSamplePool; + private Queue mSentOutputs = new LinkedList<>(); public synchronized void setCallbacks(ICodecCallbacks callbacks) throws RemoteException { mCallbacks = callbacks; @@ -187,6 +238,7 @@ import java.util.Queue; codec.configure(fmt, surface, flags); mCodec = codec; mInputProcessor = new InputProcessor(); + mSamplePool = new SamplePool(codecName); if (DEBUG) Log.d(LOGTAG, codec.toString() + " created"); return true; } catch (Exception e) { @@ -279,7 +331,7 @@ import java.util.Queue; @Override public synchronized Sample dequeueInput(int size) { - return Sample.create(); + return mInputProcessor.onAllocate(size); } @Override @@ -291,12 +343,20 @@ import java.util.Queue; @Override public synchronized void releaseOutput(Sample sample) { + try { + mSamplePool.recycleOutput(mSentOutputs.remove()); + } catch (NoSuchElementException e) { + Log.e(LOGTAG, "releaseOutput not found: " + sample + "sent: " + mSentOutputs); + } + sample.dispose(); } @Override public synchronized void release() throws RemoteException { if (DEBUG) Log.d(LOGTAG, "release " + this); releaseCodec(); + mSamplePool.reset(); + mSamplePool = null; mCallbacks.asBinder().unlinkToDeath(this, 0); mCallbacks = null; } diff --git a/mobile/android/base/java/org/mozilla/gecko/media/SamplePool.java b/mobile/android/base/java/org/mozilla/gecko/media/SamplePool.java new file mode 100644 index 000000000000..9041e37561ee --- /dev/null +++ b/mobile/android/base/java/org/mozilla/gecko/media/SamplePool.java @@ -0,0 +1,115 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.gecko.media; + +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 final String mName; + private int mNextId = 0; + private int mDefaultBufferSize = 4096; + private final List mRecycledSamples = new ArrayList<>(); + + private Impl(String name) { + mName = name; + } + + private void setDefaultBufferSize(int size) { + mDefaultBufferSize = size; + } + + private synchronized Sample allocate(int size) { + Sample sample; + 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 sample; + } + + private synchronized void recycle(Sample recycled) { + if (recycled.buffer.capacity() >= mDefaultBufferSize) { + mRecycledSamples.add(recycled); + } else { + recycled.dispose(); + } + } + + private synchronized void clear() { + for (Sample s : mRecycledSamples) { + s.dispose(); + } + + mRecycledSamples.clear(); + } + + @Override + 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 */ void setInputBufferSize(int size) { + mInputs.setDefaultBufferSize(size); + } + + /* package */ void setOutputBufferSize(int size) { + mOutputs.setDefaultBufferSize(size); + } + + /* package */ Sample obtainInput(int size) { + return mInputs.allocate(size); + } + + /* package */ Sample obtainOutput(MediaCodec.BufferInfo info) { + Sample output = mOutputs.allocate(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); + } + + /* package */ void recycleOutput(Sample sample) { + mOutputs.recycle(sample); + } + + /* package */ void reset() { + mInputs.clear(); + mOutputs.clear(); + } +} diff --git a/mobile/android/base/moz.build b/mobile/android/base/moz.build index 8ac5095e6eb4..1183191ead4e 100644 --- a/mobile/android/base/moz.build +++ b/mobile/android/base/moz.build @@ -561,6 +561,7 @@ gbjar.sources += ['java/org/mozilla/gecko/' + x for x in [ 'media/MediaManager.java', 'media/RemoteManager.java', 'media/Sample.java', + 'media/SamplePool.java', 'media/VideoPlayer.java', 'MediaCastingBar.java', 'MemoryMonitor.java',