MediaCodec: add new buffer and format APIs

Bug: 14562236
Bug: 14297827
Bug: 13008204
Bug: 10706245
Bug: 10672559
Bug: 9175531

Change-Id: Ia94d34f625e532619a9ed344eb27e0d26647982f
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index f9e4566..efdacfb 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -260,10 +260,21 @@
     return mCodec->signalEndOfInputStream();
 }
 
-status_t JMediaCodec::getOutputFormat(JNIEnv *env, jobject *format) const {
+status_t JMediaCodec::getFormat(JNIEnv *env, bool input, jobject *format) const {
     sp<AMessage> msg;
     status_t err;
-    if ((err = mCodec->getOutputFormat(&msg)) != OK) {
+    err = input ? mCodec->getInputFormat(&msg) : mCodec->getOutputFormat(&msg);
+    if (err != OK) {
+        return err;
+    }
+
+    return ConvertMessageToMap(env, msg, format);
+}
+
+status_t JMediaCodec::getOutputFormat(JNIEnv *env, size_t index, jobject *format) const {
+    sp<AMessage> msg;
+    status_t err;
+    if ((err = mCodec->getOutputFormat(index, &msg)) != OK) {
         return err;
     }
 
@@ -295,6 +306,11 @@
 
     CHECK(orderID != NULL);
 
+    jmethodID asReadOnlyBufferID = env->GetMethodID(
+            byteBufferClass.get(), "asReadOnlyBuffer", "()Ljava/nio/ByteBuffer;");
+
+    CHECK(asReadOnlyBufferID != NULL);
+
     ScopedLocalRef<jclass> byteOrderClass(
             env, env->FindClass("java/nio/ByteOrder"));
 
@@ -327,6 +343,12 @@
             env->NewDirectByteBuffer(
                 buffer->base(),
                 buffer->capacity());
+        if (!input && byteBuffer != NULL) {
+            jobject readOnlyBuffer = env->CallObjectMethod(
+                    byteBuffer, asReadOnlyBufferID);
+            env->DeleteLocalRef(byteBuffer);
+            byteBuffer = readOnlyBuffer;
+        }
         if (byteBuffer == NULL) {
             env->DeleteLocalRef(nativeByteOrderObj);
             return NO_MEMORY;
@@ -349,6 +371,130 @@
     return OK;
 }
 
+status_t JMediaCodec::getBuffer(
+        JNIEnv *env, bool input, size_t index, jobject *buf) const {
+    sp<ABuffer> buffer;
+
+    status_t err =
+        input
+            ? mCodec->getInputBuffer(index, &buffer)
+            : mCodec->getOutputBuffer(index, &buffer);
+
+    if (err != OK) {
+        return err;
+    }
+
+    ScopedLocalRef<jclass> byteBufferClass(
+            env, env->FindClass("java/nio/ByteBuffer"));
+
+    CHECK(byteBufferClass.get() != NULL);
+
+    jmethodID orderID = env->GetMethodID(
+            byteBufferClass.get(),
+            "order",
+            "(Ljava/nio/ByteOrder;)Ljava/nio/ByteBuffer;");
+
+    CHECK(orderID != NULL);
+
+    jmethodID asReadOnlyBufferID = env->GetMethodID(
+            byteBufferClass.get(), "asReadOnlyBuffer", "()Ljava/nio/ByteBuffer;");
+
+    CHECK(asReadOnlyBufferID != NULL);
+
+    jmethodID positionID = env->GetMethodID(
+            byteBufferClass.get(), "position", "(I)Ljava/nio/Buffer;");
+
+    CHECK(positionID != NULL);
+
+    jmethodID limitID = env->GetMethodID(
+            byteBufferClass.get(), "limit", "(I)Ljava/nio/Buffer;");
+
+    CHECK(limitID != NULL);
+
+    ScopedLocalRef<jclass> byteOrderClass(
+            env, env->FindClass("java/nio/ByteOrder"));
+
+    CHECK(byteOrderClass.get() != NULL);
+
+    jmethodID nativeOrderID = env->GetStaticMethodID(
+            byteOrderClass.get(), "nativeOrder", "()Ljava/nio/ByteOrder;");
+    CHECK(nativeOrderID != NULL);
+
+    jobject nativeByteOrderObj =
+        env->CallStaticObjectMethod(byteOrderClass.get(), nativeOrderID);
+    CHECK(nativeByteOrderObj != NULL);
+
+    // if this is an ABuffer that doesn't actually hold any accessible memory,
+    // use a null ByteBuffer
+    if (buffer->base() == NULL) {
+        *buf = NULL;
+        return OK;
+    }
+
+    jobject byteBuffer =
+        env->NewDirectByteBuffer(
+            buffer->base(),
+            buffer->capacity());
+    if (!input && byteBuffer != NULL) {
+        jobject readOnlyBuffer = env->CallObjectMethod(
+                byteBuffer, asReadOnlyBufferID);
+        env->DeleteLocalRef(byteBuffer);
+        byteBuffer = readOnlyBuffer;
+    }
+    if (byteBuffer == NULL) {
+        env->DeleteLocalRef(nativeByteOrderObj);
+        return NO_MEMORY;
+    }
+    jobject me = env->CallObjectMethod(
+            byteBuffer, orderID, nativeByteOrderObj);
+    env->DeleteLocalRef(me);
+    me = env->CallObjectMethod(
+            byteBuffer, positionID,
+            input ? 0 : buffer->offset());
+    env->DeleteLocalRef(me);
+    me = env->CallObjectMethod(
+            byteBuffer, limitID,
+            input ? buffer->capacity() : (buffer->offset() + buffer->size()));
+    env->DeleteLocalRef(me);
+    me = NULL;
+
+    env->DeleteLocalRef(nativeByteOrderObj);
+    nativeByteOrderObj = NULL;
+
+    *buf = byteBuffer;
+    return OK;
+}
+
+status_t JMediaCodec::getImage(
+        JNIEnv *env, bool input, size_t index, jobject *buf) const {
+    sp<ABuffer> buffer;
+
+    status_t err =
+        input
+            ? mCodec->getInputBuffer(index, &buffer)
+            : mCodec->getOutputBuffer(index, &buffer);
+
+    if (err != OK) {
+        return err;
+    }
+
+    // if this is an ABuffer that doesn't actually hold any accessible memory,
+    // use a null ByteBuffer
+    *buf = NULL;
+    if (buffer->base() == NULL) {
+        return OK;
+    }
+
+    // check if buffer is an image
+    AString imageData;
+    if (!buffer->meta()->findString("image-data", &imageData)) {
+        return OK;
+    }
+
+    return OK;
+}
+
+
 status_t JMediaCodec::getName(JNIEnv *env, jstring *nameStr) const {
     AString name;
 
@@ -959,9 +1105,9 @@
     throwExceptionAsNecessary(env, err);
 }
 
-static jobject android_media_MediaCodec_getOutputFormatNative(
-        JNIEnv *env, jobject thiz) {
-    ALOGV("android_media_MediaCodec_getOutputFormatNative");
+static jobject android_media_MediaCodec_getFormatNative(
+        JNIEnv *env, jobject thiz, jboolean input) {
+    ALOGV("android_media_MediaCodec_getFormatNative");
 
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
@@ -971,7 +1117,30 @@
     }
 
     jobject format;
-    status_t err = codec->getOutputFormat(env, &format);
+    status_t err = codec->getFormat(env, input, &format);
+
+    if (err == OK) {
+        return format;
+    }
+
+    throwExceptionAsNecessary(env, err);
+
+    return NULL;
+}
+
+static jobject android_media_MediaCodec_getOutputFormatForIndexNative(
+        JNIEnv *env, jobject thiz, jint index) {
+    ALOGV("android_media_MediaCodec_getOutputFormatForIndexNative");
+
+    sp<JMediaCodec> codec = getMediaCodec(env, thiz);
+
+    if (codec == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return NULL;
+    }
+
+    jobject format;
+    status_t err = codec->getOutputFormat(env, index, &format);
 
     if (err == OK) {
         return format;
@@ -1008,6 +1177,58 @@
     return NULL;
 }
 
+static jobject android_media_MediaCodec_getBuffer(
+        JNIEnv *env, jobject thiz, jboolean input, jint index) {
+    ALOGV("android_media_MediaCodec_getBuffer");
+
+    sp<JMediaCodec> codec = getMediaCodec(env, thiz);
+
+    if (codec == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return NULL;
+    }
+
+    jobject buffer;
+    status_t err = codec->getBuffer(env, input, index, &buffer);
+
+    if (err == OK) {
+        return buffer;
+    }
+
+    // if we're out of memory, an exception was already thrown
+    if (err != NO_MEMORY) {
+        throwExceptionAsNecessary(env, err);
+    }
+
+    return NULL;
+}
+
+static jobject android_media_MediaCodec_getImage(
+        JNIEnv *env, jobject thiz, jboolean input, jint index) {
+    ALOGV("android_media_MediaCodec_getImage");
+
+    sp<JMediaCodec> codec = getMediaCodec(env, thiz);
+
+    if (codec == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return NULL;
+    }
+
+    jobject image;
+    status_t err = codec->getImage(env, input, index, &image);
+
+    if (err == OK) {
+        return image;
+    }
+
+    // if we're out of memory, an exception was already thrown
+    if (err != NO_MEMORY) {
+        throwExceptionAsNecessary(env, err);
+    }
+
+    return NULL;
+}
+
 static jobject android_media_MediaCodec_getName(
         JNIEnv *env, jobject thiz) {
     ALOGV("android_media_MediaCodec_getName");
@@ -1168,7 +1389,7 @@
 }
 
 static JNINativeMethod gMethods[] = {
-    { "release", "()V", (void *)android_media_MediaCodec_release },
+    { "native_release", "()V", (void *)android_media_MediaCodec_release },
 
     { "native_setCallback",
       "(Landroid/media/MediaCodec$Callback;)V",
@@ -1182,20 +1403,20 @@
     { "createInputSurface", "()Landroid/view/Surface;",
       (void *)android_media_MediaCodec_createInputSurface },
 
-    { "start", "()V", (void *)android_media_MediaCodec_start },
+    { "native_start", "()V", (void *)android_media_MediaCodec_start },
     { "native_stop", "()V", (void *)android_media_MediaCodec_stop },
-    { "flush", "()V", (void *)android_media_MediaCodec_flush },
+    { "native_flush", "()V", (void *)android_media_MediaCodec_flush },
 
-    { "queueInputBuffer", "(IIIJI)V",
+    { "native_queueInputBuffer", "(IIIJI)V",
       (void *)android_media_MediaCodec_queueInputBuffer },
 
-    { "queueSecureInputBuffer", "(IILandroid/media/MediaCodec$CryptoInfo;JI)V",
+    { "native_queueSecureInputBuffer", "(IILandroid/media/MediaCodec$CryptoInfo;JI)V",
       (void *)android_media_MediaCodec_queueSecureInputBuffer },
 
-    { "dequeueInputBuffer", "(J)I",
+    { "native_dequeueInputBuffer", "(J)I",
       (void *)android_media_MediaCodec_dequeueInputBuffer },
 
-    { "dequeueOutputBuffer", "(Landroid/media/MediaCodec$BufferInfo;J)I",
+    { "native_dequeueOutputBuffer", "(Landroid/media/MediaCodec$BufferInfo;J)I",
       (void *)android_media_MediaCodec_dequeueOutputBuffer },
 
     { "releaseOutputBuffer", "(IZZJ)V",
@@ -1204,12 +1425,21 @@
     { "signalEndOfInputStream", "()V",
       (void *)android_media_MediaCodec_signalEndOfInputStream },
 
-    { "getOutputFormatNative", "()Ljava/util/Map;",
-      (void *)android_media_MediaCodec_getOutputFormatNative },
+    { "getFormatNative", "(Z)Ljava/util/Map;",
+      (void *)android_media_MediaCodec_getFormatNative },
+
+    { "getOutputFormatNative", "(I)Ljava/util/Map;",
+      (void *)android_media_MediaCodec_getOutputFormatForIndexNative },
 
     { "getBuffers", "(Z)[Ljava/nio/ByteBuffer;",
       (void *)android_media_MediaCodec_getBuffers },
 
+    { "getBuffer", "(ZI)Ljava/nio/ByteBuffer;",
+      (void *)android_media_MediaCodec_getBuffer },
+
+    { "getImage", "(ZI)Landroid/media/Image;",
+      (void *)android_media_MediaCodec_getImage },
+
     { "getName", "()Ljava/lang/String;",
       (void *)android_media_MediaCodec_getName },
 
diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h
index a70fa48..2e650e3 100644
--- a/media/jni/android_media_MediaCodec.h
+++ b/media/jni/android_media_MediaCodec.h
@@ -86,11 +86,19 @@
 
     status_t signalEndOfInputStream();
 
-    status_t getOutputFormat(JNIEnv *env, jobject *format) const;
+    status_t getFormat(JNIEnv *env, bool input, jobject *format) const;
+
+    status_t getOutputFormat(JNIEnv *env, size_t index, jobject *format) const;
 
     status_t getBuffers(
             JNIEnv *env, bool input, jobjectArray *bufArray) const;
 
+    status_t getBuffer(
+            JNIEnv *env, bool input, size_t index, jobject *buf) const;
+
+    status_t getImage(
+            JNIEnv *env, bool input, size_t index, jobject *image) const;
+
     status_t getName(JNIEnv *env, jstring *name) const;
 
     status_t setParameters(const sp<AMessage> &params);