Bug 1317628-[P2] Handling key-needed/key-added/notifying for encrypted media playback with Fennec out-of-process-decode mode on Android L. draft
authorKilik Kuo <kikuo@mozilla.com>
Thu, 22 Dec 2016 21:39:29 +0800
changeset 452877 a8fc67838e4703ef1f3fe42120ae4102c5050ca4
parent 452783 c60638268ed926688d1fb2d430d12fc7603cd8ca
child 540336 fa9b1188bc0206eab5992c1d94b1388d40bc2990
push id39526
push userbmo:kikuo@mozilla.com
push dateThu, 22 Dec 2016 13:40:07 +0000
bugs1317628
milestone53.0a1
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
mobile/android/base/java/org/mozilla/gecko/media/AsyncCodec.java
mobile/android/base/java/org/mozilla/gecko/media/Codec.java
mobile/android/base/java/org/mozilla/gecko/media/JellyBeanAsyncCodec.java
mobile/android/base/java/org/mozilla/gecko/media/MediaCodecUtil.java
mobile/android/base/java/org/mozilla/gecko/media/RemoteMediaDrmBridge.java
mobile/android/base/java/org/mozilla/gecko/media/RemoteMediaDrmBridgeStub.java
--- 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()");