Bug 1349883 - part 2: reveal more input buffer status to callbacks. r=esawin,jya
Promise based MediaDataDecoder expects one response per request, but ICodecCallbacks was not designed that way. onInputExhausted() is called only when there are none or just a few input buffers waiting to be queued, and onOutput() is called as soon as output buffers are available. It means these 2 kinds of events are usually interleaved and hard to align with pending promises. Reporting each input buffer status makes it easier for RemoteDataDecoder to resolve promise properly.
MozReview-Commit-ID: K09txmHTtmX
--- a/mobile/android/geckoview/src/main/aidl/org/mozilla/gecko/media/ICodecCallbacks.aidl
+++ b/mobile/android/geckoview/src/main/aidl/org/mozilla/gecko/media/ICodecCallbacks.aidl
@@ -4,13 +4,14 @@
package org.mozilla.gecko.media;
// Non-default types used in interface.
import org.mozilla.gecko.media.FormatParam;
import org.mozilla.gecko.media.Sample;
interface ICodecCallbacks {
- oneway void onInputExhausted();
+ oneway void onInputQueued(long timestamp);
+ oneway void onInputPending(long timestamp);
oneway void onOutputFormatChanged(in FormatParam format);
oneway void onOutput(in Sample sample);
oneway void onError(boolean fatal);
}
\ No newline at end of file
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/Codec.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/Codec.java
@@ -45,22 +45,30 @@ import java.util.concurrent.ConcurrentLi
}
@Override
public void onOutputFormatChanged(AsyncCodec codec, MediaFormat format) {
mOutputProcessor.onFormatChanged(format);
}
}
+ private static final class Input {
+ public final Sample sample;
+ public boolean reported;
+
+ public Input(final Sample sample) {
+ this.sample = sample;
+ }
+ }
+
private final class InputProcessor {
- private static final int FEW_PENDING_INPUTS = 2;
private boolean mHasInputCapacitySet;
private Queue<Integer> mAvailableInputBuffers = new LinkedList<>();
private Queue<Sample> mDequeuedSamples = new LinkedList<>();
- private Queue<Sample> mInputSamples = new LinkedList<>();
+ private Queue<Input> mInputSamples = new LinkedList<>();
private boolean mStopped;
private synchronized Sample onAllocate(int size) {
Sample sample = mSamplePool.obtainInput(size);
mDequeuedSamples.add(sample);
return sample;
}
@@ -81,17 +89,17 @@ import java.util.concurrent.ConcurrentLi
dequeued.info = sample.info;
dequeued.cryptoInfo = sample.cryptoInfo;
queueSample(dequeued);
sample.dispose();
}
private void queueSample(Sample sample) {
- if (!mInputSamples.offer(sample)) {
+ if (!mInputSamples.offer(new Input(sample))) {
reportError(Error.FATAL, new Exception("FAIL: input sample queue is full"));
return;
}
try {
feedSampleToBuffer();
} catch (Exception e) {
reportError(Error.FATAL, e);
@@ -118,17 +126,17 @@ import java.util.concurrent.ConcurrentLi
}
}
private void feedSampleToBuffer() {
while (!mAvailableInputBuffers.isEmpty() && !mInputSamples.isEmpty()) {
int index = mAvailableInputBuffers.poll();
int len = 0;
- Sample sample = mInputSamples.poll();
+ final Sample sample = mInputSamples.poll().sample;
long pts = sample.info.presentationTimeUs;
int flags = sample.info.flags;
MediaCodec.CryptoInfo cryptoInfo = sample.cryptoInfo;
if (!sample.isEOS() && sample.buffer != null) {
len = sample.info.size;
ByteBuffer buf = mCodec.getInputBuffer(index);
try {
sample.writeToByteBuffer(buf);
@@ -139,32 +147,42 @@ import java.util.concurrent.ConcurrentLi
mSamplePool.recycleInput(sample);
}
if (cryptoInfo != null && len > 0) {
mCodec.queueSecureInputBuffer(index, 0, cryptoInfo, pts, flags);
} else {
mCodec.queueInputBuffer(index, 0, len, pts, flags);
}
- }
- // To avoid input queue flood, request more input samples only when
- // there are just a few waiting to be processed.
- if (mDequeuedSamples.size() + mInputSamples.size() <= FEW_PENDING_INPUTS) {
try {
- mCallbacks.onInputExhausted();
+ mCallbacks.onInputQueued(pts);
} catch (RemoteException e) {
e.printStackTrace();
}
}
+ reportPendingInputs();
+ }
+
+ private void reportPendingInputs() {
+ try {
+ for (Input i : mInputSamples) {
+ if (!i.reported) {
+ i.reported = true;
+ mCallbacks.onInputPending(i.sample.info.presentationTimeUs);
+ }
+ }
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
}
private synchronized void reset() {
- for (Sample s : mInputSamples) {
- if (!s.isEOS()) {
- mSamplePool.recycleInput(s);
+ for (Input i : mInputSamples) {
+ if (!i.sample.isEOS()) {
+ mSamplePool.recycleInput(i.sample);
}
}
mInputSamples.clear();
for (Sample s : mDequeuedSamples) {
mSamplePool.recycleInput(s);
}
mDequeuedSamples.clear();
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/CodecProxy.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/CodecProxy.java
@@ -58,17 +58,24 @@ public final class CodecProxy {
private final Callbacks mCallbacks;
private boolean mEndOfInput;
CallbacksForwarder(Callbacks callbacks) {
mCallbacks = callbacks;
}
@Override
- public void onInputExhausted() throws RemoteException {
+ public synchronized void onInputQueued(long timestamp) throws RemoteException {
+ if (!mEndOfInput) {
+ mCallbacks.onInputExhausted();
+ }
+ }
+
+ @Override
+ public synchronized void onInputPending(long timestamp) throws RemoteException {
if (!mEndOfInput) {
mCallbacks.onInputExhausted();
}
}
@Override
public void onOutputFormatChanged(FormatParam format) throws RemoteException {
mCallbacks.onOutputFormatChanged(format.asFormat());