Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8d5e556b authored by Chong Zhang's avatar Chong Zhang
Browse files

MediaCodec async callbacks

Bug: 11990118

Change-Id: I210d4302e1fd7e1a48d2228fd3f4f20c16b18a75
parent ca249dc6
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
@@ -14289,6 +14289,7 @@ package android.media {
    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 @@ package android.media {
    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();
+116 −31
Original line number Diff line number Diff line
@@ -205,9 +205,15 @@ final public class MediaCodec {
    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 @@ final public class MediaCodec {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case EVENT_NOTIFY:
                case EVENT_CALLBACK:
                {
                    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:
                {
                    NotificationCallback cb = mNotificationCallback;
                    if (cb != null) {
                        cb.onCodecNotify(mCodec);
                    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 @@ final public class MediaCodec {
        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 @@ final public class MediaCodec {
        native_stop();

        if (mEventHandler != null) {
            mEventHandler.removeMessages(EVENT_NOTIFY);
            mEventHandler.removeMessages(EVENT_CALLBACK);
            mEventHandler.removeMessages(EVENT_SET_CALLBACK);
        }
    }

@@ -855,44 +912,72 @@ final public class MediaCodec {
    }

    /**
     * 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.
     *
     * If the client intends to use the component in asynchronous mode,
     * a valid callback should be provided before {@link #configure} is called.
     *
     * @param cb The listener that will run.
     * When asynchronous callback is enabled, the client should not call
     * {@link #dequeueInputBuffer(long)} or {@link #dequeueOutputBuffer(BufferInfo, long)}
     *
     * @hide
     * @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 callback interface. Used to notify the user asynchronously
     * of various MediaCodec events.
     */
    public static abstract class Callback {
        /**
         * Called when an input buffer becomes available.
         *
         * @param codec The MediaCodec object.
         * @param index The index of the available input buffer.
         */
        public abstract void onInputBufferAvailable(MediaCodec codec, int index);

        /**
     * 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.
         * Called when an output buffer becomes available.
         *
     * @hide
         * @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 static abstract class NotificationCallback {
        public abstract void onOutputBufferAvailable(MediaCodec codec, int index, BufferInfo info);

        /**
         * 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 the MediaCodec encountered an error
         *
         * @param codec the MediaCodec instance that has an actionable event.
         * @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 onCodecNotify(MediaCodec codec);
        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);
        }
+121 −66
Original line number Diff line number Diff line
@@ -54,7 +54,8 @@ enum {
};

enum {
    EVENT_NOTIFY = 1,
    EVENT_CALLBACK = 1,
    EVENT_SET_CALLBACK = 2,
};

struct CryptoErrorCodes {
@@ -82,9 +83,7 @@ JMediaCodec::JMediaCodec(
        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 @@ JMediaCodec::~JMediaCodec() {
    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::createInputSurface(
}

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::queueSecureInputBuffer(
}

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 JMediaCodec::dequeueOutputBuffer(
    status_t err = mCodec->dequeueOutputBuffer(
            index, &offset, &size, &timeUs, &flags, timeoutUs);

    requestActivityNotification();

    if (err != OK) {
        return err;
    }
@@ -387,67 +373,116 @@ void JMediaCodec::setVideoScalingMode(int mode) {
    }
}

void JMediaCodec::onMessageReceived(const sp<AMessage> &msg) {
    switch (msg->what()) {
        case kWhatRequestActivityNotifications:
void JMediaCodec::handleCallback(const sp<AMessage> &msg) {
    int32_t arg1, arg2 = 0;
    jobject obj = NULL;
    CHECK(msg->findInt32("callbackID", &arg1));
    JNIEnv *env = AndroidRuntime::getJNIEnv();

    switch (arg1) {
        case MediaCodec::CB_INPUT_AVAILABLE:
        {
            if (mRequestedActivityNotification) {
            CHECK(msg->findInt32("index", &arg2));
            break;
        }

            mCodec->requestActivityNotification(mActivityNotification);
            mRequestedActivityNotification = true;
        case MediaCodec::CB_OUTPUT_AVAILABLE:
        {
            CHECK(msg->findInt32("index", &arg2));

            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();
                }
                jniThrowException(env, "java/lang/IllegalStateException", NULL);
                return;
            }

            env->CallVoidMethod(obj, method, (jint)offset, (jint)size, timeUs, flags);
            break;
        }

        case kWhatActivityNotify:
        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 MediaCodec::CB_OUTPUT_FORMAT_CHANGED:
        {
                int32_t generation;
                CHECK(msg->findInt32("generation", &generation));
            sp<AMessage> format;
            CHECK(msg->findMessage("format", &format));

            if (OK != ConvertMessageToMap(env, format, &obj)) {
                jniThrowException(env, "java/lang/IllegalStateException", NULL);
                return;
            }

                if (generation != mGeneration) {
                    // stale
            break;
        }

                mRequestedActivityNotification = false;
        default:
            TRESPASS();
    }

            JNIEnv *env = AndroidRuntime::getJNIEnv();
    env->CallVoidMethod(
            mObject,
            gFields.postEventFromNativeID,
                    EVENT_NOTIFY,
                    0 /* arg1 */,
                    0 /* arg2 */,
                    NULL /* obj */);
            EVENT_CALLBACK,
            arg1,
            arg2,
            obj);

            break;
    env->DeleteLocalRef(obj);
}

        case kWhatStopActivityNotifications:
void JMediaCodec::onMessageReceived(const sp<AMessage> &msg) {
    switch (msg->what()) {
        case kWhatCallbackNotify:
        {
            uint32_t replyID;
            CHECK(msg->senderAwaitsResponse(&replyID));

            ++mGeneration;
            mRequestedActivityNotification = false;

            sp<AMessage> response = new AMessage;
            response->postReply(replyID);
            handleCallback(msg);
            break;
        }

        default:
            TRESPASS();
    }
}

void JMediaCodec::requestActivityNotification() {
    (new AMessage(kWhatRequestActivityNotifications, id()))->post();
}

}  // namespace android

////////////////////////////////////////////////////////////////////////////////
@@ -551,6 +586,22 @@ static jint throwExceptionAsNecessary(
    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 void android_media_MediaCodec_native_finalize(
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",
+5 −8
Original line number Diff line number Diff line
@@ -44,6 +44,8 @@ struct JMediaCodec : public AHandler {
    void registerSelf();
    void release();

    status_t setCallback(jobject cb);

    status_t configure(
            const sp<AMessage> &format,
            const sp<IGraphicBufferProducer> &bufferProducer,
@@ -99,12 +101,11 @@ protected:
    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 @@ private:
    sp<ALooper> mLooper;
    sp<MediaCodec> mCodec;

    sp<AMessage> mActivityNotification;
    int32_t mGeneration;
    bool mRequestedActivityNotification;

    void requestActivityNotification();
    sp<AMessage> mCallbackNotification;

    DISALLOW_EVIL_CONSTRUCTORS(JMediaCodec);
};