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);
};