Bug 1317628-[P2] Handling key-needed/key-added/notifying for encrypted media playback with Fennec out-of-process-decode mode on Android L.
MozReview-Commit-ID: HnPubxDdvBA
--- a/mobile/android/base/java/org/mozilla/gecko/media/AsyncCodec.java
+++ b/mobile/android/base/java/org/mozilla/gecko/media/AsyncCodec.java
@@ -30,9 +30,11 @@ public interface AsyncCodec {
public abstract void stop();
public abstract void flush();
public abstract void release();
public abstract ByteBuffer getInputBuffer(int index);
public abstract ByteBuffer getOutputBuffer(int index);
public abstract void queueInputBuffer(int index, int offset, int size, long presentationTimeUs, int flags);
public abstract void queueSecureInputBuffer(int index, int offset, CryptoInfo info, long presentationTimeUs, int flags);
public abstract void releaseOutputBuffer(int index, boolean render);
+
+ public abstract void setDrmStubId(String drmStubId);
}
--- a/mobile/android/base/java/org/mozilla/gecko/media/Codec.java
+++ b/mobile/android/base/java/org/mozilla/gecko/media/Codec.java
@@ -1,14 +1,17 @@
/* 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 org.mozilla.gecko.AppConstants;
+import org.mozilla.gecko.media.MediaCodecUtil;
+
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;
@@ -158,16 +161,21 @@ import java.util.concurrent.ConcurrentLi
return false;
}
feedSampleToBuffer();
return true;
}
private void feedSampleToBuffer() {
while (!mAvailableInputBuffers.isEmpty() && !mInputSamples.isEmpty()) {
+ if (AppConstants.Versions.preMarshmallow &&
+ RemoteMediaDrmBridgeStub.waitForKey(mDrmStubId)) {
+ // Wait until keys being added to schedule another run.
+ return;
+ }
int index = mAvailableInputBuffers.poll();
int len = 0;
Sample sample = mInputSamples.poll();
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;
@@ -200,16 +208,17 @@ import java.util.concurrent.ConcurrentLi
private volatile ICodecCallbacks mCallbacks;
private AsyncCodec mCodec;
private InputProcessor mInputProcessor;
private volatile boolean mFlushing = false;
private SamplePool mSamplePool;
private Queue<Sample> mSentOutputs = new ConcurrentLinkedQueue<>();
// Value will be updated after configure called.
private volatile boolean mIsAdaptivePlaybackSupported = false;
+ private String mDrmStubId;
public synchronized void setCallbacks(ICodecCallbacks callbacks) throws RemoteException {
mCallbacks = callbacks;
callbacks.asBinder().linkToDeath(this, 0);
}
// IBinder.DeathRecipient
@Override
@@ -268,16 +277,21 @@ import java.util.concurrent.ConcurrentLi
fmt.setInteger(MediaFormat.KEY_MAX_HEIGHT, 1080);
}
}
codec.configure(fmt, surface, crypto, flags);
mCodec = codec;
mInputProcessor = new InputProcessor();
mSamplePool = new SamplePool(codecName);
+
+ if (crypto != null && AppConstants.Versions.preMarshmallow) {
+ mDrmStubId = drmStubId;
+ mCodec.setDrmStubId(drmStubId);
+ }
if (DEBUG) Log.d(LOGTAG, codec.toString() + " created");
return true;
} catch (Exception e) {
if (DEBUG) Log.d(LOGTAG, "FAIL: cannot create codec -- " + codecName);
e.printStackTrace();
return false;
}
}
--- a/mobile/android/base/java/org/mozilla/gecko/media/JellyBeanAsyncCodec.java
+++ b/mobile/android/base/java/org/mozilla/gecko/media/JellyBeanAsyncCodec.java
@@ -1,38 +1,39 @@
/* 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 org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.util.HardwareCodecCapabilityUtils;
+import org.mozilla.gecko.media.MediaCodecUtil;
import android.media.MediaCodec;
import android.media.MediaCrypto;
import android.media.MediaFormat;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.view.Surface;
import java.io.IOException;
import java.nio.ByteBuffer;
+import java.util.concurrent.locks.ReentrantLock;
// Implement async API using MediaCodec sync mode (API v16).
// This class uses internal worker thread/handler (mBufferPoller) to poll
// input and output buffer and notifies the client through callbacks.
final class JellyBeanAsyncCodec implements AsyncCodec {
private static final String LOGTAG = "GeckoAsyncCodecAPIv16";
private static final boolean DEBUG = false;
- private static final int ERROR_CODEC = -10000;
-
private abstract class CancelableHandler extends Handler {
private static final int MSG_CANCELLATION = 0x434E434C; // 'CNCL'
protected CancelableHandler(Looper looper) {
super(looper);
}
protected void cancel() {
@@ -199,17 +200,17 @@ final class JellyBeanAsyncCodec implemen
case MSG_POLL_OUTPUT_BUFFERS:
pollOutputBuffer();
break;
default:
return false;
}
} catch (IllegalStateException e) {
e.printStackTrace();
- mCallbackSender.notifyError(ERROR_CODEC);
+ mCallbackSender.notifyError(MediaCodecUtil.CODEC_STATUS_ERROR);
}
return true;
}
private void pollInputBuffer() {
int result = mCodec.dequeueInputBuffer(DEQUEUE_TIMEOUT_US);
if (result >= 0) {
@@ -253,16 +254,18 @@ final class JellyBeanAsyncCodec implemen
private ByteBuffer[] mOutputBuffers;
private AsyncCodec.Callbacks mCallbacks;
private CallbackSender mCallbackSender;
private BufferPoller mBufferPoller;
private volatile boolean mInputEnded;
private volatile boolean mOutputEnded;
+ private String mDrmStubId;
+
// Must be called on a thread with looper.
/* package */ JellyBeanAsyncCodec(String name) throws IOException {
mCodec = MediaCodec.createByCodecName(name);
initBufferPoller(name + " buffer poller");
}
private void initBufferPoller(String name) {
if (mBufferPoller != null) {
@@ -331,44 +334,73 @@ final class JellyBeanAsyncCodec implemen
assertCallbacks();
mInputEnded = (flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
try {
mCodec.queueInputBuffer(index, offset, size, presentationTimeUs, flags);
} catch (IllegalStateException e) {
e.printStackTrace();
- mCallbackSender.notifyError(ERROR_CODEC);
+ mCallbackSender.notifyError(MediaCodecUtil.CODEC_STATUS_ERROR);
return;
}
mBufferPoller.schedulePolling(BufferPoller.MSG_POLL_OUTPUT_BUFFERS);
mBufferPoller.schedulePolling(BufferPoller.MSG_POLL_INPUT_BUFFERS);
}
@Override
public final void queueSecureInputBuffer(int index,
int offset,
MediaCodec.CryptoInfo cryptoInfo,
long presentationTimeUs,
int flags) {
assertCallbacks();
mInputEnded = (flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
-
- try {
- mCodec.queueSecureInputBuffer(index, offset, cryptoInfo, presentationTimeUs, flags);
- } catch (IllegalStateException e) {
- e.printStackTrace();
- mCallbackSender.notifyError(ERROR_CODEC);
+ int res = MediaCodecUtil.CODEC_STATUS_OK;
+ if (AppConstants.Versions.preMarshmallow) {
+ ReentrantLock lock = RemoteMediaDrmBridgeStub.getKeyNeededLock(mDrmStubId);
+ if (lock == null) {
+ res = MediaCodecUtil.CODEC_STATUS_ERROR;
+ } else {
+ lock.lock();
+ try {
+ mCodec.queueSecureInputBuffer(index, offset, cryptoInfo, presentationTimeUs, flags);
+ } catch (MediaCodec.CryptoException e) {
+ if (e.getErrorCode() == MediaCodec.CryptoException.ERROR_NO_KEY) {
+ if (DEBUG) Log.d(LOGTAG, "Got CryptoException.ERROR_NO_KEY");
+ RemoteMediaDrmBridgeStub.notifyKeyNeeded(mDrmStubId);
+ if (RemoteMediaDrmBridgeStub.waitForKey(mDrmStubId)) {
+ if (DEBUG) Log.d(LOGTAG, "going to doWait ...");
+ RemoteMediaDrmBridgeStub.doWait(mDrmStubId);
+ }
+ if (DEBUG) Log.d(LOGTAG, "going to queueSecureInputBuffer 2nd try.");
+ mCodec.queueSecureInputBuffer(index, offset, cryptoInfo, presentationTimeUs, flags);
+ } else {
+ res = MediaCodecUtil.CODEC_STATUS_ERROR;
+ }
+ } catch (Exception e) {
+ res = MediaCodecUtil.CODEC_STATUS_ERROR;
+ } finally {
+ lock.unlock();
+ }
+ }
+ } else {
+ res = MediaCodecUtil.queueSecureInputBufferV23(mCodec, index,
+ offset, cryptoInfo,
+ presentationTimeUs,
+ flags);
+ }
+ if (res != MediaCodecUtil.CODEC_STATUS_OK) {
+ mCallbackSender.notifyError(res);
return;
}
-
+ mBufferPoller.schedulePolling(BufferPoller.MSG_POLL_OUTPUT_BUFFERS);
mBufferPoller.schedulePolling(BufferPoller.MSG_POLL_INPUT_BUFFERS);
- mBufferPoller.schedulePolling(BufferPoller.MSG_POLL_OUTPUT_BUFFERS);
}
@Override
public final void releaseOutputBuffer(int index, boolean render) {
assertCallbacks();
mCodec.releaseOutputBuffer(index, render);
}
@@ -418,16 +450,21 @@ final class JellyBeanAsyncCodec implemen
assertCallbacks();
cancelPendingTasks();
mCallbackSender = null;
mCodec.release();
stopBufferPoller();
}
+ @Override
+ public void setDrmStubId(String drmStubId) {
+ mDrmStubId = drmStubId;
+ }
+
private void stopBufferPoller() {
if (mBufferPoller == null) {
Log.e(LOGTAG, "no initialized poller.");
return;
}
mBufferPoller.getLooper().quit();
mBufferPoller = null;
--- a/mobile/android/base/java/org/mozilla/gecko/media/MediaCodecUtil.java
+++ b/mobile/android/base/java/org/mozilla/gecko/media/MediaCodecUtil.java
@@ -40,22 +40,22 @@ public final class MediaCodecUtil {
} catch (Exception e) {
return CODEC_STATUS_ERROR;
}
return CODEC_STATUS_OK;
}
@WrapForJNI
public static int queueSecureInputBufferV21(MediaCodec codec,
- String drmStubId,
- int index,
- int offset,
- MediaCodec.CryptoInfo cryptoInfo,
- long presentationTimeUs,
- int flags) {
+ String drmStubId,
+ int index,
+ int offset,
+ MediaCodec.CryptoInfo cryptoInfo,
+ long presentationTimeUs,
+ int flags) {
ReentrantLock lock = MediaDrmProxy.getKeyNeededLock(drmStubId);
if (lock == null) {
return CODEC_STATUS_ERROR;
}
int status = CODEC_STATUS_OK;
lock.lock();
try {
codec.queueSecureInputBuffer(index, offset, cryptoInfo, presentationTimeUs, flags);
--- a/mobile/android/base/java/org/mozilla/gecko/media/RemoteMediaDrmBridge.java
+++ b/mobile/android/base/java/org/mozilla/gecko/media/RemoteMediaDrmBridge.java
@@ -171,9 +171,9 @@ final class RemoteMediaDrmBridge impleme
}
@Override
public ReentrantLock getKeyNeededLock() {
// Should not be used in this case.
assertTrue(false);
return null;
}
-}
\ No newline at end of file
+}
--- a/mobile/android/base/java/org/mozilla/gecko/media/RemoteMediaDrmBridgeStub.java
+++ b/mobile/android/base/java/org/mozilla/gecko/media/RemoteMediaDrmBridgeStub.java
@@ -39,16 +39,17 @@ final class RemoteMediaDrmBridgeStub ext
private MediaCrypto getMediaCryptoFromBridge() {
return mBridge != null ? mBridge.getMediaCrypto() : null;
}
public static synchronized MediaCrypto getMediaCrypto(String stubId) {
if (DEBUG) Log.d(LOGTAG, "getMediaCrypto()");
RemoteMediaDrmBridgeStub bridgeStub = sBridgeStubMap.get(stubId);
if (bridgeStub == null) {
+ if (DEBUG) Log.d(LOGTAG, "getMediaCrypto() error, find no stub !");
return null;
}
return bridgeStub.getMediaCryptoFromBridge();
}
public static void notifyKeyNeeded(String stubId) {
assertTrue(AppConstants.Versions.preMarshmallow);
if (DEBUG) Log.d(LOGTAG, "notifyKeyNeeded()");