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

Commit 073204bb authored by Chong Zhang's avatar Chong Zhang Committed by Android (Google) Code Review
Browse files

Merge "MediaCodec async callbacks"

parents 34257d85 8d5e556b
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
@@ -14292,6 +14292,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();
@@ -14322,6 +14323,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);
};