MediaCodec async callbacks

Bug: 11990118

Change-Id: I210d4302e1fd7e1a48d2228fd3f4f20c16b18a75
diff --git a/api/current.txt b/api/current.txt
index c5fc314..1450496 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -14289,6 +14289,7 @@
     method public final void release();
     method public final void releaseOutputBuffer(int, boolean);
     method public final void releaseOutputBuffer(int, long);
+    method public void setCallback(android.media.MediaCodec.Callback);
     method public final void setParameters(android.os.Bundle);
     method public final void setVideoScalingMode(int);
     method public final void signalEndOfInputStream();
@@ -14319,6 +14320,14 @@
     field public int size;
   }
 
+  public static abstract class MediaCodec.Callback {
+    ctor public MediaCodec.Callback();
+    method public abstract void onError(android.media.MediaCodec, int, int);
+    method public abstract void onInputBufferAvailable(android.media.MediaCodec, int);
+    method public abstract void onOutputBufferAvailable(android.media.MediaCodec, int, android.media.MediaCodec.BufferInfo);
+    method public abstract void onOutputFormatChanged(android.media.MediaCodec, android.media.MediaFormat);
+  }
+
   public static final class MediaCodec.CodecException extends java.lang.IllegalStateException {
     ctor public MediaCodec.CodecException(int, int, java.lang.String);
     method public int getErrorCode();
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index e9a5f3f..3c04a16 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -205,9 +205,15 @@
     public static final int BUFFER_FLAG_END_OF_STREAM         = 4;
 
     private EventHandler mEventHandler;
-    private volatile NotificationCallback mNotificationCallback;
+    private Callback mCallback;
 
-    static final int EVENT_NOTIFY = 1;
+    private static final int EVENT_CALLBACK = 1;
+    private static final int EVENT_SET_CALLBACK = 2;
+
+    private static final int CB_INPUT_AVAILABLE = 1;
+    private static final int CB_OUTPUT_AVAILABLE = 2;
+    private static final int CB_ERROR = 3;
+    private static final int CB_OUTPUT_FORMAT_CHANGE = 4;
 
     private class EventHandler extends Handler {
         private MediaCodec mCodec;
@@ -220,12 +226,60 @@
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
-                case EVENT_NOTIFY:
+                case EVENT_CALLBACK:
                 {
-                    NotificationCallback cb = mNotificationCallback;
-                    if (cb != null) {
-                        cb.onCodecNotify(mCodec);
-                    }
+                    handleCallback(msg);
+                    break;
+                }
+                case EVENT_SET_CALLBACK:
+                {
+                    mCallback = (MediaCodec.Callback) msg.obj;
+                    break;
+                }
+                default:
+                {
+                    break;
+                }
+            }
+        }
+
+        private void handleCallback(Message msg) {
+            if (mCallback == null) {
+                return;
+            }
+
+            switch (msg.arg1) {
+                case CB_INPUT_AVAILABLE:
+                {
+                    mCallback.onInputBufferAvailable(mCodec, msg.arg2 /* index */);
+                    break;
+                }
+
+                case CB_OUTPUT_AVAILABLE:
+                {
+                    mCallback.onOutputBufferAvailable(
+                            mCodec,
+                            msg.arg2 /* index */,
+                            (MediaCodec.BufferInfo) msg.obj);
+                    break;
+                }
+
+                case CB_ERROR:
+                {
+                    mCallback.onError(mCodec,
+                            msg.arg2 /* error */, (Integer) msg.obj /* actionCode */);
+                    break;
+                }
+
+                case CB_OUTPUT_FORMAT_CHANGE:
+                {
+                    mCallback.onOutputFormatChanged(mCodec,
+                            new MediaFormat((Map<String, Object>) msg.obj));
+                    break;
+                }
+
+                default:
+                {
                     break;
                 }
             }
@@ -360,6 +414,8 @@
         native_configure(keys, values, surface, crypto, flags);
     }
 
+    private native final void native_setCallback(Callback cb);
+
     private native final void native_configure(
             String[] keys, Object[] values,
             Surface surface, MediaCrypto crypto, int flags);
@@ -398,7 +454,8 @@
         native_stop();
 
         if (mEventHandler != null) {
-            mEventHandler.removeMessages(EVENT_NOTIFY);
+            mEventHandler.removeMessages(EVENT_CALLBACK);
+            mEventHandler.removeMessages(EVENT_SET_CALLBACK);
         }
     }
 
@@ -855,44 +912,72 @@
     }
 
     /**
-     * Sets the codec listener for actionable MediaCodec events.
-     * <p>Call this method with a null listener to stop receiving event notifications.
+     * Sets an asynchronous callback for actionable MediaCodec events.
      *
-     * @param cb The listener that will run.
+     * If the client intends to use the component in asynchronous mode,
+     * a valid callback should be provided before {@link #configure} is called.
      *
-     * @hide
+     * When asynchronous callback is enabled, the client should not call
+     * {@link #dequeueInputBuffer(long)} or {@link #dequeueOutputBuffer(BufferInfo, long)}
+     *
+     * @param cb The callback that will run.
      */
-    public void setNotificationCallback(NotificationCallback cb) {
-        mNotificationCallback = cb;
+    public void setCallback(/* MediaCodec. */ Callback cb) {
+        if (mEventHandler != null) {
+            // set java callback on handler
+            Message msg = mEventHandler.obtainMessage(EVENT_SET_CALLBACK, 0, 0, cb);
+            mEventHandler.sendMessage(msg);
+
+            // set native handler here, don't post to handler because
+            // it may cause the callback to be delayed and set in a wrong state,
+            // and MediaCodec is already doing it on looper.
+            native_setCallback(cb);
+        }
     }
 
     /**
-     * MediaCodec listener interface.  Used to notify the user of MediaCodec
-     * when there are available input and/or output buffers, a change in
-     * configuration or when a codec error happened.
-     *
-     * @hide
+     * MediaCodec callback interface. Used to notify the user asynchronously
+     * of various MediaCodec events.
      */
-    public static abstract class NotificationCallback {
+    public static abstract class Callback {
         /**
-         * Called on the listener to notify that there is an actionable
-         * MediaCodec event.  The application should call {@link #dequeueOutputBuffer}
-         * to receive the configuration change event, codec error or an
-         * available output buffer.  It should also call  {@link #dequeueInputBuffer}
-         * to receive any available input buffer.  For best performance, it
-         * is recommended to exhaust both available input and output buffers in
-         * the handling of a single callback, by calling the dequeue methods
-         * repeatedly with a zero timeout until {@link #INFO_TRY_AGAIN_LATER} is returned.
+         * Called when an input buffer becomes available.
          *
-         * @param codec the MediaCodec instance that has an actionable event.
-         *
+         * @param codec The MediaCodec object.
+         * @param index The index of the available input buffer.
          */
-        public abstract void onCodecNotify(MediaCodec codec);
+        public abstract void onInputBufferAvailable(MediaCodec codec, int index);
+
+        /**
+         * Called when an output buffer becomes available.
+         *
+         * @param codec The MediaCodec object.
+         * @param index The index of the available output buffer.
+         * @param info Info regarding the available output buffer {@link MediaCodec.BufferInfo}.
+         */
+        public abstract void onOutputBufferAvailable(MediaCodec codec, int index, BufferInfo info);
+
+        /**
+         * Called when the MediaCodec encountered an error
+         *
+         * @param codec The MediaCodec object.
+         * @param error a device specific error code.
+         * @param actionCode a value for use in {@link MediaCodec.CodecException}.
+         */
+        public abstract void onError(MediaCodec codec, int error, int actionCode);
+
+        /**
+         * Called when the output format has changed
+         *
+         * @param codec The MediaCodec object.
+         * @param format The new output format.
+         */
+        public abstract void onOutputFormatChanged(MediaCodec codec, MediaFormat format);
     }
 
     private void postEventFromNative(
             int what, int arg1, int arg2, Object obj) {
-        if (mEventHandler != null && mNotificationCallback != null) {
+        if (mEventHandler != null) {
             Message msg = mEventHandler.obtainMessage(what, arg1, arg2, obj);
             mEventHandler.sendMessage(msg);
         }
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 4a7c096..f9e4566 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -54,7 +54,8 @@
 };
 
 enum {
-    EVENT_NOTIFY = 1,
+    EVENT_CALLBACK = 1,
+    EVENT_SET_CALLBACK = 2,
 };
 
 struct CryptoErrorCodes {
@@ -82,9 +83,7 @@
         JNIEnv *env, jobject thiz,
         const char *name, bool nameIsType, bool encoder)
     : mClass(NULL),
-      mObject(NULL),
-      mGeneration(1),
-      mRequestedActivityNotification(false) {
+      mObject(NULL) {
     jclass clazz = env->GetObjectClass(thiz);
     CHECK(clazz != NULL);
 
@@ -151,6 +150,18 @@
     mClass = NULL;
 }
 
+status_t JMediaCodec::setCallback(jobject cb) {
+    if (cb != NULL) {
+        if (mCallbackNotification == NULL) {
+            mCallbackNotification = new AMessage(kWhatCallbackNotify, id());
+        }
+    } else {
+        mCallbackNotification.clear();
+    }
+
+    return mCodec->setCallback(mCallbackNotification);
+}
+
 status_t JMediaCodec::configure(
         const sp<AMessage> &format,
         const sp<IGraphicBufferProducer> &bufferProducer,
@@ -173,32 +184,13 @@
 }
 
 status_t JMediaCodec::start() {
-    status_t err = mCodec->start();
-
-    if (err != OK) {
-        return err;
-    }
-
-    mActivityNotification = new AMessage(kWhatActivityNotify, id());
-    mActivityNotification->setInt32("generation", mGeneration);
-
-    requestActivityNotification();
-
-    return err;
+    return mCodec->start();
 }
 
 status_t JMediaCodec::stop() {
     mSurfaceTextureClient.clear();
 
-    status_t err = mCodec->stop();
-
-    sp<AMessage> msg = new AMessage(kWhatStopActivityNotifications, id());
-    sp<AMessage> response;
-    msg->postAndAwaitResponse(&response);
-
-    mActivityNotification.clear();
-
-    return err;
+    return mCodec->stop();
 }
 
 status_t JMediaCodec::flush() {
@@ -230,11 +222,7 @@
 }
 
 status_t JMediaCodec::dequeueInputBuffer(size_t *index, int64_t timeoutUs) {
-    status_t err = mCodec->dequeueInputBuffer(index, timeoutUs);
-
-    requestActivityNotification();
-
-    return err;
+    return mCodec->dequeueInputBuffer(index, timeoutUs);
 }
 
 status_t JMediaCodec::dequeueOutputBuffer(
@@ -245,8 +233,6 @@
     status_t err = mCodec->dequeueOutputBuffer(
             index, &offset, &size, &timeUs, &flags, timeoutUs);
 
-    requestActivityNotification();
-
     if (err != OK) {
         return err;
     }
@@ -387,65 +373,114 @@
     }
 }
 
-void JMediaCodec::onMessageReceived(const sp<AMessage> &msg) {
-    switch (msg->what()) {
-        case kWhatRequestActivityNotifications:
-        {
-            if (mRequestedActivityNotification) {
-                break;
-            }
+void JMediaCodec::handleCallback(const sp<AMessage> &msg) {
+    int32_t arg1, arg2 = 0;
+    jobject obj = NULL;
+    CHECK(msg->findInt32("callbackID", &arg1));
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
 
-            mCodec->requestActivityNotification(mActivityNotification);
-            mRequestedActivityNotification = true;
+    switch (arg1) {
+        case MediaCodec::CB_INPUT_AVAILABLE:
+        {
+            CHECK(msg->findInt32("index", &arg2));
             break;
         }
 
-        case kWhatActivityNotify:
+        case MediaCodec::CB_OUTPUT_AVAILABLE:
         {
-            {
-                int32_t generation;
-                CHECK(msg->findInt32("generation", &generation));
+            CHECK(msg->findInt32("index", &arg2));
 
-                if (generation != mGeneration) {
-                    // stale
-                    break;
+            size_t size, offset;
+            int64_t timeUs;
+            uint32_t flags;
+            CHECK(msg->findSize("size", &size));
+            CHECK(msg->findSize("offset", &offset));
+            CHECK(msg->findInt64("timeUs", &timeUs));
+            CHECK(msg->findInt32("flags", (int32_t *)&flags));
+
+            ScopedLocalRef<jclass> clazz(
+                    env, env->FindClass("android/media/MediaCodec$BufferInfo"));
+            jmethodID ctor = env->GetMethodID(clazz.get(), "<init>", "()V");
+            jmethodID method = env->GetMethodID(clazz.get(), "set", "(IIJI)V");
+
+            obj = env->NewObject(clazz.get(), ctor);
+
+            if (obj == NULL) {
+                if (env->ExceptionCheck()) {
+                    ALOGE("Could not create MediaCodec.BufferInfo.");
+                    env->ExceptionClear();
                 }
-
-                mRequestedActivityNotification = false;
+                jniThrowException(env, "java/lang/IllegalStateException", NULL);
+                return;
             }
 
-            JNIEnv *env = AndroidRuntime::getJNIEnv();
-            env->CallVoidMethod(
-                    mObject,
-                    gFields.postEventFromNativeID,
-                    EVENT_NOTIFY,
-                    0 /* arg1 */,
-                    0 /* arg2 */,
-                    NULL /* obj */);
+            env->CallVoidMethod(obj, method, (jint)offset, (jint)size, timeUs, flags);
+            break;
+        }
+
+        case MediaCodec::CB_ERROR:
+        {
+            CHECK(msg->findInt32("err", &arg2));
+
+            int32_t actionCode;
+            CHECK(msg->findInt32("actionCode", &actionCode));
+
+            // use Integer object to pass the action code
+            ScopedLocalRef<jclass> clazz(
+                    env, env->FindClass("java/lang/Integer"));
+            jmethodID ctor = env->GetMethodID(clazz.get(), "<init>", "(I)V");
+            obj = env->NewObject(clazz.get(), ctor, actionCode);
+
+            if (obj == NULL) {
+                if (env->ExceptionCheck()) {
+                    ALOGE("Could not create Integer object for actionCode.");
+                    env->ExceptionClear();
+                }
+                jniThrowException(env, "java/lang/IllegalStateException", NULL);
+                return;
+            }
 
             break;
         }
 
-        case kWhatStopActivityNotifications:
+        case MediaCodec::CB_OUTPUT_FORMAT_CHANGED:
         {
-            uint32_t replyID;
-            CHECK(msg->senderAwaitsResponse(&replyID));
+            sp<AMessage> format;
+            CHECK(msg->findMessage("format", &format));
 
-            ++mGeneration;
-            mRequestedActivityNotification = false;
+            if (OK != ConvertMessageToMap(env, format, &obj)) {
+                jniThrowException(env, "java/lang/IllegalStateException", NULL);
+                return;
+            }
 
-            sp<AMessage> response = new AMessage;
-            response->postReply(replyID);
             break;
         }
 
         default:
             TRESPASS();
     }
+
+    env->CallVoidMethod(
+            mObject,
+            gFields.postEventFromNativeID,
+            EVENT_CALLBACK,
+            arg1,
+            arg2,
+            obj);
+
+    env->DeleteLocalRef(obj);
 }
 
-void JMediaCodec::requestActivityNotification() {
-    (new AMessage(kWhatRequestActivityNotifications, id()))->post();
+void JMediaCodec::onMessageReceived(const sp<AMessage> &msg) {
+    switch (msg->what()) {
+        case kWhatCallbackNotify:
+        {
+            handleCallback(msg);
+            break;
+        }
+        default:
+            TRESPASS();
+    }
 }
 
 }  // namespace android
@@ -551,6 +586,22 @@
     return 0;
 }
 
+static void android_media_MediaCodec_native_setCallback(
+        JNIEnv *env,
+        jobject thiz,
+        jobject cb) {
+    sp<JMediaCodec> codec = getMediaCodec(env, thiz);
+
+    if (codec == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return;
+    }
+
+    status_t err = codec->setCallback(cb);
+
+    throwExceptionAsNecessary(env, err);
+}
+
 static void android_media_MediaCodec_native_configure(
         JNIEnv *env,
         jobject thiz,
@@ -1119,6 +1170,10 @@
 static JNINativeMethod gMethods[] = {
     { "release", "()V", (void *)android_media_MediaCodec_release },
 
+    { "native_setCallback",
+      "(Landroid/media/MediaCodec$Callback;)V",
+      (void *)android_media_MediaCodec_native_setCallback },
+
     { "native_configure",
       "([Ljava/lang/String;[Ljava/lang/Object;Landroid/view/Surface;"
       "Landroid/media/MediaCrypto;I)V",
diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h
index bf9f4ea..a70fa48 100644
--- a/media/jni/android_media_MediaCodec.h
+++ b/media/jni/android_media_MediaCodec.h
@@ -44,6 +44,8 @@
     void registerSelf();
     void release();
 
+    status_t setCallback(jobject cb);
+
     status_t configure(
             const sp<AMessage> &format,
             const sp<IGraphicBufferProducer> &bufferProducer,
@@ -99,12 +101,11 @@
     virtual ~JMediaCodec();
 
     virtual void onMessageReceived(const sp<AMessage> &msg);
+    void handleCallback(const sp<AMessage> &msg);
 
 private:
     enum {
-        kWhatActivityNotify,
-        kWhatRequestActivityNotifications,
-        kWhatStopActivityNotifications,
+        kWhatCallbackNotify,
     };
 
     jclass mClass;
@@ -114,11 +115,7 @@
     sp<ALooper> mLooper;
     sp<MediaCodec> mCodec;
 
-    sp<AMessage> mActivityNotification;
-    int32_t mGeneration;
-    bool mRequestedActivityNotification;
-
-    void requestActivityNotification();
+    sp<AMessage> mCallbackNotification;
 
     DISALLOW_EVIL_CONSTRUCTORS(JMediaCodec);
 };